From 13f482e2e4a25d8d18981fb1d6dc17b6f8a08e78 Mon Sep 17 00:00:00 2001 From: Laurent Bigonville Date: Sun, 13 Dec 2020 14:45:17 +0000 Subject: [PATCH] Import glade_3.38.2.orig.tar.xz [dgit import orig glade_3.38.2.orig.tar.xz] --- AUTHORS | 9 + CONTRIBUTING.md | 70 + COPYING | 5 + COPYING.GPL | 280 + COPYING.LGPL | 437 + ChangeLog.old.gz | Bin 0 -> 149641 bytes INTERNALS | 132 + MAINTAINERS | 11 + NEWS | 1898 +++ README.md | 116 + TODO | 45 + data/.gitignore | 4 + data/gettext/its/glade-catalog.its | 18 + data/gettext/its/glade-catalog.loc | 6 + data/icons/deprecated-16x16.png | Bin 0 -> 437 bytes data/icons/deprecated-22x22.png | Bin 0 -> 671 bytes data/icons/devhelp.png | Bin 0 -> 651 bytes data/icons/fixed-bg.png | Bin 0 -> 216 bytes data/icons/glade.ico | Bin 0 -> 15086 bytes .../hicolor/scalable/apps/org.gnome.Glade.svg | 1845 +++ .../symbolic/apps/glade-brand-symbolic.svg | 29 + .../apps/org.gnome.Glade-symbolic.svg | 125 + data/icons/placeholder.png | Bin 0 -> 240 bytes data/icons/plus.png | Bin 0 -> 147 bytes data/meson.build | 67 + data/org.gnome.Glade.appdata.xml.in | 164 + data/org.gnome.Glade.desktop.in.in | 19 + doc/.gitignore | 1 + doc/catalogintro.sgml | 212 + doc/gladegjs.sgml | 99 + doc/gladepython.sgml | 92 + doc/gladeui-docs.xml | 90 + doc/gladeui-sections.txt | 1545 ++ doc/gladeui.types | 18 + doc/meson.build | 49 + doc/properties.sgml | 272 + doc/version.xml.in | 1 + doc/widgetclasses.sgml | 555 + glade.doap | 40 + gladeui/.gitignore | 6 + gladeui/atk.png | Bin 0 -> 914 bytes gladeui/glade-accumulators.c | 115 + gladeui/glade-accumulators.h | 39 + gladeui/glade-adaptor-chooser-widget.c | 650 + gladeui/glade-adaptor-chooser-widget.h | 64 + gladeui/glade-adaptor-chooser-widget.ui | 96 + gladeui/glade-adaptor-chooser.c | 436 + gladeui/glade-adaptor-chooser.h | 43 + gladeui/glade-adaptor-chooser.ui | 114 + gladeui/glade-app.c | 970 ++ gladeui/glade-app.h | 91 + gladeui/glade-base-editor.c | 2174 +++ gladeui/glade-base-editor.h | 80 + gladeui/glade-base-editor.ui | 259 + gladeui/glade-builtins.c | 653 + gladeui/glade-builtins.h | 61 + gladeui/glade-catalog.c | 1098 ++ gladeui/glade-catalog.h | 91 + gladeui/glade-cell-renderer-icon.c | 239 + gladeui/glade-cell-renderer-icon.h | 54 + gladeui/glade-clipboard.c | 224 + gladeui/glade-clipboard.h | 28 + gladeui/glade-command.c | 3033 ++++ gladeui/glade-command.h | 160 + gladeui/glade-cursor.c | 200 + gladeui/glade-cursor.h | 56 + gladeui/glade-debug.c | 107 + gladeui/glade-debug.h | 35 + gladeui/glade-design-layout.c | 2520 ++++ gladeui/glade-design-layout.css | 55 + gladeui/glade-design-layout.h | 50 + gladeui/glade-design-private.h | 65 + gladeui/glade-design-view.c | 866 ++ gladeui/glade-design-view.h | 53 + gladeui/glade-displayable-values.c | 200 + gladeui/glade-displayable-values.h | 37 + gladeui/glade-dnd.c | 175 + gladeui/glade-dnd.h | 52 + gladeui/glade-drag.c | 86 + gladeui/glade-drag.h | 74 + gladeui/glade-editable.c | 221 + gladeui/glade-editable.h | 46 + gladeui/glade-editor-property.c | 3859 +++++ gladeui/glade-editor-property.h | 115 + gladeui/glade-editor-skeleton.c | 266 + gladeui/glade-editor-skeleton.h | 44 + gladeui/glade-editor-table.c | 667 + gladeui/glade-editor-table.h | 48 + gladeui/glade-editor.c | 1365 ++ gladeui/glade-editor.h | 42 + gladeui/glade-editor.ui | 318 + gladeui/glade-id-allocator.c | 202 + gladeui/glade-id-allocator.h | 46 + gladeui/glade-inspector.c | 1144 ++ gladeui/glade-inspector.h | 65 + gladeui/glade-marshallers.list | 25 + gladeui/glade-name-context.c | 196 + gladeui/glade-name-context.h | 29 + gladeui/glade-named-icon-chooser-dialog.c | 1781 +++ gladeui/glade-named-icon-chooser-dialog.h | 61 + gladeui/glade-object-stub.c | 211 + gladeui/glade-object-stub.h | 36 + gladeui/glade-palette.c | 900 ++ gladeui/glade-palette.h | 103 + gladeui/glade-path.h | 408 + gladeui/glade-placeholder.c | 689 + gladeui/glade-placeholder.h | 70 + gladeui/glade-popup.c | 680 + gladeui/glade-popup.h | 36 + gladeui/glade-preview-template.c | 301 + gladeui/glade-preview-template.h | 39 + gladeui/glade-preview-tokens.h | 11 + gladeui/glade-preview.c | 362 + gladeui/glade-preview.h | 62 + gladeui/glade-previewer-main.c | 447 + gladeui/glade-previewer.c | 869 ++ gladeui/glade-previewer.h | 97 + gladeui/glade-previewer.rc.in | 29 + gladeui/glade-private.h | 124 + gladeui/glade-project-properties.c | 1342 ++ gladeui/glade-project-properties.h | 37 + gladeui/glade-project-properties.ui | 714 + gladeui/glade-project.c | 5520 +++++++ gladeui/glade-project.h | 289 + gladeui/glade-property-def.c | 2443 ++++ gladeui/glade-property-def.h | 188 + gladeui/glade-property-label.c | 723 + gladeui/glade-property-label.h | 82 + gladeui/glade-property-label.ui | 64 + gladeui/glade-property-shell.c | 568 + gladeui/glade-property-shell.h | 77 + gladeui/glade-property.c | 1712 +++ gladeui/glade-property.h | 188 + gladeui/glade-signal-def.c | 330 + gladeui/glade-signal-def.h | 97 + gladeui/glade-signal-editor.c | 1553 ++ gladeui/glade-signal-editor.h | 36 + gladeui/glade-signal-model.c | 1007 ++ gladeui/glade-signal-model.h | 71 + gladeui/glade-signal.c | 659 + gladeui/glade-signal.h | 76 + gladeui/glade-template.c | 222 + gladeui/glade-tsort.c | 179 + gladeui/glade-tsort.h | 50 + gladeui/glade-utils.c | 2195 +++ gladeui/glade-utils.h | 142 + gladeui/glade-widget-action.c | 376 + gladeui/glade-widget-action.h | 98 + gladeui/glade-widget-adaptor.c | 4653 ++++++ gladeui/glade-widget-adaptor.h | 904 ++ gladeui/glade-widget.c | 5170 +++++++ gladeui/glade-widget.h | 419 + gladeui/glade-xml-utils.c | 1128 ++ gladeui/glade-xml-utils.h | 285 + gladeui/glade.h | 54 + gladeui/glade_plugin.def | 27 + gladeui/gladeui-enum-types.c.template | 36 + gladeui/gladeui-enum-types.h.template | 25 + gladeui/gladeui-resources.gresource.xml | 13 + gladeui/gladeui.rc.in | 30 + gladeui/icon-naming-spec.c | 346 + gladeui/meson.build | 286 + help/C/figures/main-window.png | Bin 0 -> 45943 bytes help/C/index.docbook | 484 + help/C/legal.xml | 73 + help/ChangeLog | 102 + help/LINGUAS | 28 + help/bg/bg.po | 868 ++ help/ca/ca.po | 1059 ++ help/cs/cs.po | 963 ++ help/cs/figures/main-window.png | Bin 0 -> 48957 bytes help/da/da.po | 957 ++ help/da/figures/main-window.png | Bin 0 -> 38939 bytes help/de/de.po | 984 ++ help/de/figures/main-window.png | Bin 0 -> 48687 bytes help/el/el.po | 1075 ++ help/en_GB/en_GB.po | 532 + help/es/es.po | 1072 ++ help/es/figures/main-window.png | Bin 0 -> 48280 bytes help/eu/eu.po | 522 + help/fr/figures/main-window.png | Bin 0 -> 50142 bytes help/fr/fr.po | 973 ++ help/gl/gl.po | 997 ++ help/hi/hi.po | 521 + help/hu/hu.po | 974 ++ help/id/id.po | 963 ++ help/it/it.po | 866 ++ help/ja/ja.po | 580 + help/ko/ko.po | 924 ++ help/meson.build | 11 + help/oc/oc.po | 686 + help/pl/figures/main-window.png | Bin 0 -> 46375 bytes help/pl/pl.po | 960 ++ help/pt_BR/figures/main-window.png | Bin 0 -> 47318 bytes help/pt_BR/pt_BR.po | 1092 ++ help/ru/figures/main-window.png | Bin 0 -> 46437 bytes help/ru/ru.po | 991 ++ help/sl/sl.po | 542 + help/sv/figures/main-window.png | Bin 0 -> 46288 bytes help/sv/sv.po | 1063 ++ help/uk/uk.po | 959 ++ help/zh_CN/zh_CN.po | 979 ++ man/glade-previewer.xml | 105 + man/glade.xml | 114 + man/meson.build | 33 + meson.build | 242 + meson_options.txt | 10 + meson_post_install.py | 12 + org.gnome.Glade.json | 45 + plugins/gjs/glade-gjs.c | 107 + plugins/gjs/meson.build | 12 + plugins/glade-catalog.dtd | 153 + plugins/gladeui/glade-glade-editor-skeleton.c | 115 + plugins/gladeui/glade-glade-property-shell.c | 36 + plugins/gladeui/gladeui.xml | 94 + plugins/gladeui/meson.build | 17 + plugins/gtk+/.gitignore | 2 + plugins/gtk+/glade-about-dialog-editor.c | 213 + plugins/gtk+/glade-about-dialog-editor.h | 57 + plugins/gtk+/glade-about-dialog-editor.ui | 681 + plugins/gtk+/glade-accels.c | 934 ++ plugins/gtk+/glade-accels.h | 65 + plugins/gtk+/glade-action-bar-editor.c | 160 + plugins/gtk+/glade-action-bar-editor.h | 57 + plugins/gtk+/glade-action-bar-editor.ui | 130 + plugins/gtk+/glade-action-editor.c | 145 + plugins/gtk+/glade-action-editor.h | 57 + plugins/gtk+/glade-action-editor.ui | 614 + plugins/gtk+/glade-activatable-editor.c | 111 + plugins/gtk+/glade-activatable-editor.h | 58 + plugins/gtk+/glade-activatable-editor.ui | 186 + .../gtk+/glade-app-chooser-button-editor.c | 68 + .../gtk+/glade-app-chooser-button-editor.h | 56 + .../gtk+/glade-app-chooser-button-editor.ui | 217 + .../gtk+/glade-app-chooser-widget-editor.c | 68 + .../gtk+/glade-app-chooser-widget-editor.h | 56 + .../gtk+/glade-app-chooser-widget-editor.ui | 258 + plugins/gtk+/glade-arrow-editor.c | 71 + plugins/gtk+/glade-arrow-editor.h | 68 + plugins/gtk+/glade-arrow-editor.ui | 62 + plugins/gtk+/glade-attributes.c | 1159 ++ plugins/gtk+/glade-attributes.h | 37 + plugins/gtk+/glade-box-editor.c | 165 + plugins/gtk+/glade-box-editor.h | 56 + plugins/gtk+/glade-box-editor.ui | 228 + plugins/gtk+/glade-button-editor.c | 402 + plugins/gtk+/glade-button-editor.h | 56 + plugins/gtk+/glade-button-editor.ui | 720 + plugins/gtk+/glade-cell-renderer-editor.c | 616 + plugins/gtk+/glade-cell-renderer-editor.h | 68 + plugins/gtk+/glade-column-types.c | 910 ++ plugins/gtk+/glade-column-types.h | 50 + plugins/gtk+/glade-combo-box-editor.c | 68 + plugins/gtk+/glade-combo-box-editor.h | 56 + plugins/gtk+/glade-combo-box-editor.ui | 561 + plugins/gtk+/glade-combo-box-text-editor.c | 68 + plugins/gtk+/glade-combo-box-text-editor.h | 56 + plugins/gtk+/glade-combo-box-text-editor.ui | 346 + plugins/gtk+/glade-entry-editor.c | 638 + plugins/gtk+/glade-entry-editor.h | 56 + plugins/gtk+/glade-entry-editor.ui | 1462 ++ plugins/gtk+/glade-eprop-enum-int.c | 443 + plugins/gtk+/glade-eprop-enum-int.h | 31 + .../gtk+/glade-file-chooser-button-editor.c | 48 + .../gtk+/glade-file-chooser-button-editor.h | 57 + .../gtk+/glade-file-chooser-button-editor.ui | 207 + .../gtk+/glade-file-chooser-dialog-editor.c | 48 + .../gtk+/glade-file-chooser-dialog-editor.h | 57 + .../gtk+/glade-file-chooser-dialog-editor.ui | 54 + plugins/gtk+/glade-file-chooser-editor.c | 48 + plugins/gtk+/glade-file-chooser-editor.h | 57 + plugins/gtk+/glade-file-chooser-editor.ui | 306 + .../gtk+/glade-file-chooser-widget-editor.c | 48 + .../gtk+/glade-file-chooser-widget-editor.h | 57 + .../gtk+/glade-file-chooser-widget-editor.ui | 61 + plugins/gtk+/glade-fixed.c | 1244 ++ plugins/gtk+/glade-fixed.h | 91 + plugins/gtk+/glade-font-button-editor.c | 48 + plugins/gtk+/glade-font-button-editor.h | 57 + plugins/gtk+/glade-font-button-editor.ui | 199 + .../gtk+/glade-font-chooser-dialog-editor.c | 48 + .../gtk+/glade-font-chooser-dialog-editor.h | 57 + .../gtk+/glade-font-chooser-dialog-editor.ui | 54 + plugins/gtk+/glade-font-chooser-editor.c | 48 + plugins/gtk+/glade-font-chooser-editor.h | 57 + plugins/gtk+/glade-font-chooser-editor.ui | 146 + .../gtk+/glade-font-chooser-widget-editor.c | 48 + .../gtk+/glade-font-chooser-widget-editor.h | 57 + .../gtk+/glade-font-chooser-widget-editor.ui | 61 + plugins/gtk+/glade-grid-editor.c | 69 + plugins/gtk+/glade-grid-editor.h | 56 + plugins/gtk+/glade-grid-editor.ui | 358 + plugins/gtk+/glade-gtk-about-dialog.c | 70 + plugins/gtk+/glade-gtk-action-bar.c | 498 + plugins/gtk+/glade-gtk-action-group.c | 243 + plugins/gtk+/glade-gtk-action-widgets.c | 219 + plugins/gtk+/glade-gtk-action-widgets.h | 49 + plugins/gtk+/glade-gtk-action.c | 76 + plugins/gtk+/glade-gtk-adjustment.c | 160 + plugins/gtk+/glade-gtk-app-chooser-button.c | 55 + plugins/gtk+/glade-gtk-app-chooser-widget.c | 40 + plugins/gtk+/glade-gtk-arrow.c | 42 + plugins/gtk+/glade-gtk-assistant.c | 424 + plugins/gtk+/glade-gtk-bin.c | 44 + plugins/gtk+/glade-gtk-box.c | 853 ++ plugins/gtk+/glade-gtk-button.c | 367 + plugins/gtk+/glade-gtk-button.h | 34 + plugins/gtk+/glade-gtk-cell-layout.c | 511 + plugins/gtk+/glade-gtk-cell-layout.h | 47 + plugins/gtk+/glade-gtk-cell-renderer.c | 413 + plugins/gtk+/glade-gtk-cell-renderer.h | 33 + plugins/gtk+/glade-gtk-combo-box-text.c | 261 + plugins/gtk+/glade-gtk-combo-box.c | 114 + plugins/gtk+/glade-gtk-container.c | 246 + plugins/gtk+/glade-gtk-dialog.c | 223 + plugins/gtk+/glade-gtk-dialog.h | 34 + plugins/gtk+/glade-gtk-entry-buffer.c | 89 + plugins/gtk+/glade-gtk-entry.c | 305 + plugins/gtk+/glade-gtk-expander.c | 155 + plugins/gtk+/glade-gtk-file-chooser-widget.c | 78 + plugins/gtk+/glade-gtk-fixed-layout.c | 148 + plugins/gtk+/glade-gtk-flow-box.c | 262 + plugins/gtk+/glade-gtk-font-chooser-widget.c | 40 + plugins/gtk+/glade-gtk-frame.c | 206 + plugins/gtk+/glade-gtk-frame.h | 39 + plugins/gtk+/glade-gtk-grid.c | 1109 ++ plugins/gtk+/glade-gtk-header-bar.c | 595 + plugins/gtk+/glade-gtk-icon-factory.c | 488 + plugins/gtk+/glade-gtk-icon-view.c | 40 + plugins/gtk+/glade-gtk-image-menu-item.c | 243 + plugins/gtk+/glade-gtk-image.c | 257 + plugins/gtk+/glade-gtk-image.h | 37 + plugins/gtk+/glade-gtk-info-bar.c | 63 + plugins/gtk+/glade-gtk-label.c | 567 + plugins/gtk+/glade-gtk-level-bar.c | 40 + plugins/gtk+/glade-gtk-list-box.c | 399 + plugins/gtk+/glade-gtk-list-store.c | 667 + plugins/gtk+/glade-gtk-marshallers.list | 24 + plugins/gtk+/glade-gtk-menu-bar.c | 143 + plugins/gtk+/glade-gtk-menu-item.c | 239 + plugins/gtk+/glade-gtk-menu-shell.c | 640 + plugins/gtk+/glade-gtk-menu-shell.h | 67 + plugins/gtk+/glade-gtk-menu-tool-button.c | 107 + plugins/gtk+/glade-gtk-menu.c | 45 + plugins/gtk+/glade-gtk-message-dialog.c | 218 + plugins/gtk+/glade-gtk-model-button.c | 52 + plugins/gtk+/glade-gtk-notebook.c | 1091 ++ plugins/gtk+/glade-gtk-notebook.h | 38 + plugins/gtk+/glade-gtk-overlay.c | 193 + plugins/gtk+/glade-gtk-paned.c | 162 + plugins/gtk+/glade-gtk-popover-menu.c | 564 + plugins/gtk+/glade-gtk-popover.c | 82 + plugins/gtk+/glade-gtk-progress-bar.c | 69 + plugins/gtk+/glade-gtk-radio-button.c | 50 + plugins/gtk+/glade-gtk-radio-menu-item.c | 55 + plugins/gtk+/glade-gtk-recent-chooser-menu.c | 51 + .../gtk+/glade-gtk-recent-chooser-widget.c | 40 + plugins/gtk+/glade-gtk-recent-file-filter.c | 281 + .../gtk+/glade-gtk-resources.gresource.xml | 59 + plugins/gtk+/glade-gtk-revealer.c | 39 + plugins/gtk+/glade-gtk-scale.c | 69 + plugins/gtk+/glade-gtk-scrollbar.c | 40 + plugins/gtk+/glade-gtk-scrolled-window.c | 75 + plugins/gtk+/glade-gtk-searchbar.c | 126 + plugins/gtk+/glade-gtk-size-group.c | 181 + plugins/gtk+/glade-gtk-spin-button.c | 79 + plugins/gtk+/glade-gtk-stack-switcher.c | 40 + plugins/gtk+/glade-gtk-stack.c | 563 + plugins/gtk+/glade-gtk-switch.c | 50 + plugins/gtk+/glade-gtk-table.c | 722 + plugins/gtk+/glade-gtk-text-buffer.c | 85 + plugins/gtk+/glade-gtk-text-tag-table.c | 172 + plugins/gtk+/glade-gtk-text-view.c | 79 + plugins/gtk+/glade-gtk-tool-button.c | 214 + plugins/gtk+/glade-gtk-tool-item-group.c | 171 + plugins/gtk+/glade-gtk-tool-item.c | 73 + plugins/gtk+/glade-gtk-tool-palette.c | 243 + plugins/gtk+/glade-gtk-toolbar.c | 315 + plugins/gtk+/glade-gtk-tree-view.c | 311 + plugins/gtk+/glade-gtk-tree-view.h | 33 + plugins/gtk+/glade-gtk-viewport.c | 40 + plugins/gtk+/glade-gtk-widget.c | 1147 ++ plugins/gtk+/glade-gtk-window.c | 396 + plugins/gtk+/glade-gtk.c | 69 + plugins/gtk+/glade-gtk.h | 55 + plugins/gtk+/glade-header-bar-editor.c | 217 + plugins/gtk+/glade-header-bar-editor.h | 57 + plugins/gtk+/glade-header-bar-editor.ui | 279 + plugins/gtk+/glade-icon-factory-editor.c | 168 + plugins/gtk+/glade-icon-factory-editor.h | 58 + plugins/gtk+/glade-icon-sources.c | 1068 ++ plugins/gtk+/glade-icon-sources.h | 49 + plugins/gtk+/glade-icon-view-editor.c | 70 + plugins/gtk+/glade-icon-view-editor.h | 57 + plugins/gtk+/glade-icon-view-editor.ui | 536 + plugins/gtk+/glade-image-editor.c | 316 + plugins/gtk+/glade-image-editor.h | 63 + plugins/gtk+/glade-image-editor.ui | 409 + plugins/gtk+/glade-image-item-editor.c | 409 + plugins/gtk+/glade-image-item-editor.h | 64 + plugins/gtk+/glade-label-editor.c | 419 + plugins/gtk+/glade-label-editor.h | 68 + plugins/gtk+/glade-label-editor.ui | 680 + plugins/gtk+/glade-layout-editor.c | 70 + plugins/gtk+/glade-layout-editor.h | 68 + plugins/gtk+/glade-layout-editor.ui | 61 + plugins/gtk+/glade-level-bar-editor.c | 68 + plugins/gtk+/glade-level-bar-editor.h | 57 + plugins/gtk+/glade-level-bar-editor.ui | 316 + plugins/gtk+/glade-message-dialog-editor.c | 48 + plugins/gtk+/glade-message-dialog-editor.h | 57 + plugins/gtk+/glade-message-dialog-editor.ui | 276 + plugins/gtk+/glade-misc-editor.c | 48 + plugins/gtk+/glade-misc-editor.h | 56 + plugins/gtk+/glade-misc-editor.ui | 229 + plugins/gtk+/glade-model-button-editor.c | 69 + plugins/gtk+/glade-model-button-editor.h | 57 + plugins/gtk+/glade-model-button-editor.ui | 262 + plugins/gtk+/glade-model-data.c | 1278 ++ plugins/gtk+/glade-model-data.h | 77 + plugins/gtk+/glade-notebook-editor.c | 200 + plugins/gtk+/glade-notebook-editor.h | 56 + plugins/gtk+/glade-notebook-editor.ui | 368 + plugins/gtk+/glade-popover-editor.c | 69 + plugins/gtk+/glade-popover-editor.h | 57 + plugins/gtk+/glade-popover-editor.ui | 196 + plugins/gtk+/glade-popover-menu-editor.c | 69 + plugins/gtk+/glade-popover-menu-editor.h | 57 + plugins/gtk+/glade-popover-menu-editor.ui | 136 + plugins/gtk+/glade-progress-bar-editor.c | 68 + plugins/gtk+/glade-progress-bar-editor.h | 57 + plugins/gtk+/glade-progress-bar-editor.ui | 339 + plugins/gtk+/glade-real-tree-view-editor.c | 70 + plugins/gtk+/glade-real-tree-view-editor.h | 68 + plugins/gtk+/glade-real-tree-view-editor.ui | 539 + plugins/gtk+/glade-recent-action-editor.c | 48 + plugins/gtk+/glade-recent-action-editor.h | 57 + plugins/gtk+/glade-recent-action-editor.ui | 54 + .../gtk+/glade-recent-chooser-dialog-editor.c | 48 + .../gtk+/glade-recent-chooser-dialog-editor.h | 57 + .../glade-recent-chooser-dialog-editor.ui | 54 + plugins/gtk+/glade-recent-chooser-editor.c | 97 + plugins/gtk+/glade-recent-chooser-editor.h | 57 + plugins/gtk+/glade-recent-chooser-editor.ui | 319 + .../gtk+/glade-recent-chooser-menu-editor.c | 48 + .../gtk+/glade-recent-chooser-menu-editor.h | 57 + .../gtk+/glade-recent-chooser-menu-editor.ui | 62 + .../gtk+/glade-recent-chooser-widget-editor.c | 48 + .../gtk+/glade-recent-chooser-widget-editor.h | 57 + .../glade-recent-chooser-widget-editor.ui | 61 + plugins/gtk+/glade-scale-button-editor.c | 80 + plugins/gtk+/glade-scale-button-editor.h | 57 + plugins/gtk+/glade-scale-button-editor.ui | 284 + plugins/gtk+/glade-scale-editor.c | 69 + plugins/gtk+/glade-scale-editor.h | 56 + plugins/gtk+/glade-scale-editor.ui | 561 + plugins/gtk+/glade-scrollable-editor.c | 48 + plugins/gtk+/glade-scrollable-editor.h | 56 + plugins/gtk+/glade-scrollable-editor.ui | 192 + plugins/gtk+/glade-scrollbar-editor.c | 69 + plugins/gtk+/glade-scrollbar-editor.h | 56 + plugins/gtk+/glade-scrollbar-editor.ui | 418 + plugins/gtk+/glade-scrolled-window-editor.c | 68 + plugins/gtk+/glade-scrolled-window-editor.h | 57 + plugins/gtk+/glade-scrolled-window-editor.ui | 506 + plugins/gtk+/glade-spin-button-editor.c | 47 + plugins/gtk+/glade-spin-button-editor.h | 57 + plugins/gtk+/glade-spin-button-editor.ui | 351 + plugins/gtk+/glade-stack-editor.c | 68 + plugins/gtk+/glade-stack-editor.h | 56 + plugins/gtk+/glade-stack-editor.ui | 245 + plugins/gtk+/glade-stack-switcher-editor.c | 68 + plugins/gtk+/glade-stack-switcher-editor.h | 56 + plugins/gtk+/glade-stack-switcher-editor.ui | 193 + plugins/gtk+/glade-store-editor.c | 201 + plugins/gtk+/glade-store-editor.h | 58 + plugins/gtk+/glade-string-list.c | 773 + plugins/gtk+/glade-string-list.h | 46 + plugins/gtk+/glade-text-view-editor.c | 68 + plugins/gtk+/glade-text-view-editor.h | 57 + plugins/gtk+/glade-text-view-editor.ui | 681 + plugins/gtk+/glade-tool-button-editor.c | 341 + plugins/gtk+/glade-tool-button-editor.h | 63 + plugins/gtk+/glade-tool-button-editor.ui | 516 + plugins/gtk+/glade-tool-item-group-editor.c | 283 + plugins/gtk+/glade-tool-item-group-editor.h | 61 + plugins/gtk+/glade-tool-palette-editor.c | 68 + plugins/gtk+/glade-tool-palette-editor.h | 57 + plugins/gtk+/glade-tool-palette-editor.ui | 61 + plugins/gtk+/glade-treeview-editor.c | 242 + plugins/gtk+/glade-treeview-editor.h | 59 + plugins/gtk+/glade-viewport-editor.c | 70 + plugins/gtk+/glade-viewport-editor.h | 68 + plugins/gtk+/glade-viewport-editor.ui | 61 + plugins/gtk+/glade-widget-editor.c | 298 + plugins/gtk+/glade-widget-editor.h | 56 + plugins/gtk+/glade-widget-editor.ui | 1257 ++ plugins/gtk+/glade-window-editor.c | 291 + plugins/gtk+/glade-window-editor.h | 57 + plugins/gtk+/glade-window-editor.ui | 792 + plugins/gtk+/gtk+.xml | 5454 +++++++ plugins/gtk+/gtkunixprint.xml | 38 + .../icons/16x16/widget-gtk-aboutdialog.png | Bin 0 -> 221 bytes .../icons/16x16/widget-gtk-accelgroup.png | Bin 0 -> 310 bytes .../icons/16x16/widget-gtk-accellabel.png | Bin 0 -> 113 bytes .../gtk+/icons/16x16/widget-gtk-action.png | Bin 0 -> 362 bytes .../gtk+/icons/16x16/widget-gtk-actionbar.png | Bin 0 -> 340 bytes .../icons/16x16/widget-gtk-actiongroup.png | Bin 0 -> 450 bytes .../icons/16x16/widget-gtk-adjustment.png | Bin 0 -> 273 bytes .../gtk+/icons/16x16/widget-gtk-alignment.png | Bin 0 -> 141 bytes .../16x16/widget-gtk-appchooserbutton.png | Bin 0 -> 289 bytes .../16x16/widget-gtk-appchooserdialog.png | Bin 0 -> 316 bytes .../16x16/widget-gtk-appchooserwidget.png | Bin 0 -> 302 bytes plugins/gtk+/icons/16x16/widget-gtk-arrow.png | Bin 0 -> 124 bytes .../icons/16x16/widget-gtk-aspectframe.png | Bin 0 -> 188 bytes .../gtk+/icons/16x16/widget-gtk-assistant.png | Bin 0 -> 179 bytes plugins/gtk+/icons/16x16/widget-gtk-box.png | Bin 0 -> 137 bytes .../gtk+/icons/16x16/widget-gtk-button.png | Bin 0 -> 220 bytes .../gtk+/icons/16x16/widget-gtk-buttonbox.png | Bin 0 -> 128 bytes .../gtk+/icons/16x16/widget-gtk-calendar.png | Bin 0 -> 152 bytes .../icons/16x16/widget-gtk-cellareabox.png | Bin 0 -> 198 bytes .../16x16/widget-gtk-cellrendereraccel.png | Bin 0 -> 248 bytes .../16x16/widget-gtk-cellrenderercombo.png | Bin 0 -> 268 bytes .../16x16/widget-gtk-cellrendererpixbuf.png | Bin 0 -> 284 bytes .../16x16/widget-gtk-cellrendererprogress.png | Bin 0 -> 273 bytes .../16x16/widget-gtk-cellrendererspin.png | Bin 0 -> 264 bytes .../16x16/widget-gtk-cellrendererspinner.png | Bin 0 -> 407 bytes .../16x16/widget-gtk-cellrenderertext.png | Bin 0 -> 248 bytes .../16x16/widget-gtk-cellrenderertoggle.png | Bin 0 -> 266 bytes .../icons/16x16/widget-gtk-checkbutton.png | Bin 0 -> 155 bytes .../icons/16x16/widget-gtk-checkmenuitem.png | Bin 0 -> 177 bytes .../icons/16x16/widget-gtk-colorbutton.png | Bin 0 -> 199 bytes .../icons/16x16/widget-gtk-colorselection.png | Bin 0 -> 312 bytes .../16x16/widget-gtk-colorselectiondialog.png | Bin 0 -> 332 bytes .../gtk+/icons/16x16/widget-gtk-combobox.png | Bin 0 -> 153 bytes .../icons/16x16/widget-gtk-comboboxtext.png | Bin 0 -> 313 bytes .../gtk+/icons/16x16/widget-gtk-custom.png | Bin 0 -> 130 bytes .../gtk+/icons/16x16/widget-gtk-default.png | Bin 0 -> 215 bytes .../gtk+/icons/16x16/widget-gtk-dialog.png | Bin 0 -> 212 bytes .../icons/16x16/widget-gtk-drawingarea.png | Bin 0 -> 384 bytes plugins/gtk+/icons/16x16/widget-gtk-entry.png | Bin 0 -> 113 bytes .../icons/16x16/widget-gtk-entrybuffer.png | Bin 0 -> 177 bytes .../16x16/widget-gtk-entrycompletion.png | Bin 0 -> 278 bytes .../gtk+/icons/16x16/widget-gtk-eventbox.png | Bin 0 -> 223 bytes .../gtk+/icons/16x16/widget-gtk-expander.png | Bin 0 -> 141 bytes .../16x16/widget-gtk-filechooserbutton.png | Bin 0 -> 234 bytes .../16x16/widget-gtk-filechooserdialog.png | Bin 0 -> 227 bytes .../16x16/widget-gtk-filechooserwidget.png | Bin 0 -> 271 bytes .../icons/16x16/widget-gtk-filefilter.png | Bin 0 -> 381 bytes plugins/gtk+/icons/16x16/widget-gtk-fixed.png | Bin 0 -> 128 bytes .../gtk+/icons/16x16/widget-gtk-flowbox.png | Bin 0 -> 381 bytes .../icons/16x16/widget-gtk-flowboxchild.png | Bin 0 -> 365 bytes .../icons/16x16/widget-gtk-fontbutton.png | Bin 0 -> 291 bytes .../icons/16x16/widget-gtk-fontselection.png | Bin 0 -> 286 bytes .../16x16/widget-gtk-fontselectiondialog.png | Bin 0 -> 322 bytes plugins/gtk+/icons/16x16/widget-gtk-frame.png | Bin 0 -> 128 bytes .../gtk+/icons/16x16/widget-gtk-glarea.png | Bin 0 -> 460 bytes plugins/gtk+/icons/16x16/widget-gtk-grid.png | Bin 0 -> 112 bytes .../icons/16x16/widget-gtk-gtktextview.png | Bin 0 -> 1419 bytes .../gtk+/icons/16x16/widget-gtk-handlebox.png | Bin 0 -> 156 bytes plugins/gtk+/icons/16x16/widget-gtk-hbox.png | Bin 0 -> 106 bytes .../icons/16x16/widget-gtk-hbuttonbox.png | Bin 0 -> 128 bytes .../gtk+/icons/16x16/widget-gtk-headerbar.png | Bin 0 -> 186 bytes .../gtk+/icons/16x16/widget-gtk-hpaned.png | Bin 0 -> 107 bytes .../gtk+/icons/16x16/widget-gtk-hscale.png | Bin 0 -> 165 bytes .../icons/16x16/widget-gtk-hscrollbar.png | Bin 0 -> 145 bytes .../icons/16x16/widget-gtk-hseparator.png | Bin 0 -> 97 bytes .../icons/16x16/widget-gtk-iconfactory.png | Bin 0 -> 379 bytes .../gtk+/icons/16x16/widget-gtk-iconview.png | Bin 0 -> 241 bytes plugins/gtk+/icons/16x16/widget-gtk-image.png | Bin 0 -> 367 bytes .../icons/16x16/widget-gtk-imagemenuitem.png | Bin 0 -> 181 bytes .../gtk+/icons/16x16/widget-gtk-infobar.png | Bin 0 -> 314 bytes .../icons/16x16/widget-gtk-inputdialog.png | Bin 0 -> 192 bytes plugins/gtk+/icons/16x16/widget-gtk-label.png | Bin 0 -> 113 bytes .../gtk+/icons/16x16/widget-gtk-layout.png | Bin 0 -> 159 bytes .../gtk+/icons/16x16/widget-gtk-levelbar.png | Bin 0 -> 250 bytes .../icons/16x16/widget-gtk-linkbutton.png | Bin 0 -> 121 bytes .../gtk+/icons/16x16/widget-gtk-listbox.png | Bin 0 -> 208 bytes .../icons/16x16/widget-gtk-listboxrow.png | Bin 0 -> 184 bytes .../gtk+/icons/16x16/widget-gtk-liststore.png | Bin 0 -> 180 bytes .../icons/16x16/widget-gtk-lockbutton.png | Bin 0 -> 274 bytes plugins/gtk+/icons/16x16/widget-gtk-menu.png | Bin 0 -> 147 bytes .../gtk+/icons/16x16/widget-gtk-menubar.png | Bin 0 -> 155 bytes .../icons/16x16/widget-gtk-menubutton.png | Bin 0 -> 563 bytes .../gtk+/icons/16x16/widget-gtk-menuitem.png | Bin 0 -> 114 bytes .../icons/16x16/widget-gtk-menutoolbutton.png | Bin 0 -> 156 bytes .../icons/16x16/widget-gtk-messagedialog.png | Bin 0 -> 224 bytes .../icons/16x16/widget-gtk-modelbutton.png | Bin 0 -> 306 bytes .../gtk+/icons/16x16/widget-gtk-notebook.png | Bin 0 -> 166 bytes .../16x16/widget-gtk-offscreenwindow.png | Bin 0 -> 223 bytes .../gtk+/icons/16x16/widget-gtk-overlay.png | Bin 0 -> 221 bytes .../16x16/widget-gtk-pagesetupdialog.png | Bin 0 -> 267 bytes plugins/gtk+/icons/16x16/widget-gtk-paned.png | Bin 0 -> 107 bytes .../icons/16x16/widget-gtk-placessidebar.png | Bin 0 -> 222 bytes .../gtk+/icons/16x16/widget-gtk-popover.png | Bin 0 -> 311 bytes .../icons/16x16/widget-gtk-popovermenu.png | Bin 0 -> 276 bytes .../icons/16x16/widget-gtk-printdialog.png | Bin 0 -> 323 bytes .../icons/16x16/widget-gtk-progressbar.png | Bin 0 -> 240 bytes .../icons/16x16/widget-gtk-radioaction.png | Bin 0 -> 464 bytes .../icons/16x16/widget-gtk-radiobutton.png | Bin 0 -> 285 bytes .../icons/16x16/widget-gtk-radiomenuitem.png | Bin 0 -> 239 bytes .../16x16/widget-gtk-radiotoolbutton.png | Bin 0 -> 281 bytes .../icons/16x16/widget-gtk-recentaction.png | Bin 0 -> 311 bytes .../icons/16x16/widget-gtk-recentchooser.png | Bin 0 -> 330 bytes .../16x16/widget-gtk-recentchooserdialog.png | Bin 0 -> 225 bytes .../16x16/widget-gtk-recentchoosermenu.png | Bin 0 -> 322 bytes .../icons/16x16/widget-gtk-recentfilter.png | Bin 0 -> 372 bytes .../icons/16x16/widget-gtk-recentmanager.png | Bin 0 -> 418 bytes .../gtk+/icons/16x16/widget-gtk-revealer.png | Bin 0 -> 217 bytes plugins/gtk+/icons/16x16/widget-gtk-scale.png | Bin 0 -> 177 bytes .../icons/16x16/widget-gtk-scalebutton.png | Bin 0 -> 485 bytes .../gtk+/icons/16x16/widget-gtk-scrollbar.png | Bin 0 -> 260 bytes .../icons/16x16/widget-gtk-scrolledwindow.png | Bin 0 -> 254 bytes .../gtk+/icons/16x16/widget-gtk-searchbar.png | Bin 0 -> 257 bytes .../icons/16x16/widget-gtk-searchentry.png | Bin 0 -> 191 bytes .../gtk+/icons/16x16/widget-gtk-separator.png | Bin 0 -> 99 bytes .../16x16/widget-gtk-separatormenuitem.png | Bin 0 -> 114 bytes .../16x16/widget-gtk-separatortoolitem.png | Bin 0 -> 89 bytes .../gtk+/icons/16x16/widget-gtk-sizegroup.png | Bin 0 -> 244 bytes .../icons/16x16/widget-gtk-spinbutton.png | Bin 0 -> 155 bytes .../gtk+/icons/16x16/widget-gtk-spinner.png | Bin 0 -> 917 bytes plugins/gtk+/icons/16x16/widget-gtk-stack.png | Bin 0 -> 181 bytes .../icons/16x16/widget-gtk-stacksidebar.png | Bin 0 -> 212 bytes .../icons/16x16/widget-gtk-stackswitcher.png | Bin 0 -> 488 bytes .../gtk+/icons/16x16/widget-gtk-statusbar.png | Bin 0 -> 107 bytes .../icons/16x16/widget-gtk-statusicon.png | Bin 0 -> 298 bytes .../gtk+/icons/16x16/widget-gtk-switch.png | Bin 0 -> 293 bytes plugins/gtk+/icons/16x16/widget-gtk-table.png | Bin 0 -> 112 bytes .../16x16/widget-gtk-tearoffmenuitem.png | Bin 0 -> 188 bytes .../icons/16x16/widget-gtk-textbuffer.png | Bin 0 -> 157 bytes .../gtk+/icons/16x16/widget-gtk-texttag.png | Bin 0 -> 325 bytes .../icons/16x16/widget-gtk-texttagtable.png | Bin 0 -> 361 bytes .../gtk+/icons/16x16/widget-gtk-textview.png | Bin 0 -> 131 bytes .../icons/16x16/widget-gtk-toggleaction.png | Bin 0 -> 368 bytes .../icons/16x16/widget-gtk-togglebutton.png | Bin 0 -> 217 bytes .../16x16/widget-gtk-toggletoolbutton.png | Bin 0 -> 150 bytes .../gtk+/icons/16x16/widget-gtk-toolbar.png | Bin 0 -> 284 bytes .../icons/16x16/widget-gtk-toolbutton.png | Bin 0 -> 141 bytes .../gtk+/icons/16x16/widget-gtk-toolitem.png | Bin 0 -> 127 bytes .../icons/16x16/widget-gtk-toolitemgroup.png | Bin 0 -> 304 bytes .../icons/16x16/widget-gtk-toolpalette.png | Bin 0 -> 311 bytes .../16x16/widget-gtk-treemodelfilter.png | Bin 0 -> 313 bytes .../icons/16x16/widget-gtk-treemodelsort.png | Bin 0 -> 250 bytes .../icons/16x16/widget-gtk-treeselection.png | Bin 0 -> 208 bytes .../gtk+/icons/16x16/widget-gtk-treestore.png | Bin 0 -> 200 bytes .../gtk+/icons/16x16/widget-gtk-treeview.png | Bin 0 -> 148 bytes .../icons/16x16/widget-gtk-treeviewcolumn.png | Bin 0 -> 255 bytes .../gtk+/icons/16x16/widget-gtk-uimanager.png | Bin 0 -> 392 bytes plugins/gtk+/icons/16x16/widget-gtk-vbox.png | Bin 0 -> 137 bytes .../icons/16x16/widget-gtk-vbuttonbox.png | Bin 0 -> 124 bytes .../gtk+/icons/16x16/widget-gtk-viewport.png | Bin 0 -> 156 bytes .../icons/16x16/widget-gtk-volumebutton.png | Bin 0 -> 544 bytes .../gtk+/icons/16x16/widget-gtk-vpaned.png | Bin 0 -> 137 bytes .../gtk+/icons/16x16/widget-gtk-vscale.png | Bin 0 -> 177 bytes .../icons/16x16/widget-gtk-vscrollbar.png | Bin 0 -> 260 bytes .../icons/16x16/widget-gtk-vseparator.png | Bin 0 -> 99 bytes .../gtk+/icons/16x16/widget-gtk-window.png | Bin 0 -> 146 bytes .../icons/16x16/widget-gtk-windowgroup.png | Bin 0 -> 325 bytes .../icons/22x22/widget-gtk-aboutdialog.png | Bin 0 -> 236 bytes .../icons/22x22/widget-gtk-accelgroup.png | Bin 0 -> 307 bytes .../icons/22x22/widget-gtk-accellabel.png | Bin 0 -> 131 bytes .../gtk+/icons/22x22/widget-gtk-action.png | Bin 0 -> 246 bytes .../gtk+/icons/22x22/widget-gtk-actionbar.png | Bin 0 -> 285 bytes .../icons/22x22/widget-gtk-actiongroup.png | Bin 0 -> 320 bytes .../icons/22x22/widget-gtk-adjustment.png | Bin 0 -> 303 bytes .../gtk+/icons/22x22/widget-gtk-alignment.png | Bin 0 -> 160 bytes .../22x22/widget-gtk-appchooserbutton.png | Bin 0 -> 299 bytes .../22x22/widget-gtk-appchooserdialog.png | Bin 0 -> 434 bytes .../22x22/widget-gtk-appchooserwidget.png | Bin 0 -> 373 bytes plugins/gtk+/icons/22x22/widget-gtk-arrow.png | Bin 0 -> 134 bytes .../icons/22x22/widget-gtk-aspectframe.png | Bin 0 -> 229 bytes .../gtk+/icons/22x22/widget-gtk-assistant.png | Bin 0 -> 219 bytes plugins/gtk+/icons/22x22/widget-gtk-box.png | Bin 0 -> 142 bytes .../gtk+/icons/22x22/widget-gtk-button.png | Bin 0 -> 181 bytes .../gtk+/icons/22x22/widget-gtk-buttonbox.png | Bin 0 -> 136 bytes .../gtk+/icons/22x22/widget-gtk-calendar.png | Bin 0 -> 161 bytes .../icons/22x22/widget-gtk-cellareabox.png | Bin 0 -> 206 bytes .../22x22/widget-gtk-cellrendereraccel.png | Bin 0 -> 259 bytes .../22x22/widget-gtk-cellrenderercombo.png | Bin 0 -> 302 bytes .../22x22/widget-gtk-cellrendererpixbuf.png | Bin 0 -> 295 bytes .../22x22/widget-gtk-cellrendererprogress.png | Bin 0 -> 284 bytes .../22x22/widget-gtk-cellrendererspin.png | Bin 0 -> 312 bytes .../22x22/widget-gtk-cellrendererspinner.png | Bin 0 -> 575 bytes .../22x22/widget-gtk-cellrenderertext.png | Bin 0 -> 256 bytes .../22x22/widget-gtk-cellrenderertoggle.png | Bin 0 -> 294 bytes .../icons/22x22/widget-gtk-checkbutton.png | Bin 0 -> 179 bytes .../icons/22x22/widget-gtk-checkmenuitem.png | Bin 0 -> 202 bytes .../icons/22x22/widget-gtk-colorbutton.png | Bin 0 -> 168 bytes .../icons/22x22/widget-gtk-colorselection.png | Bin 0 -> 458 bytes .../22x22/widget-gtk-colorselectiondialog.png | Bin 0 -> 467 bytes .../gtk+/icons/22x22/widget-gtk-combobox.png | Bin 0 -> 263 bytes .../icons/22x22/widget-gtk-comboboxtext.png | Bin 0 -> 313 bytes .../gtk+/icons/22x22/widget-gtk-custom.png | Bin 0 -> 138 bytes .../gtk+/icons/22x22/widget-gtk-default.png | Bin 0 -> 116 bytes .../gtk+/icons/22x22/widget-gtk-dialog.png | Bin 0 -> 283 bytes .../icons/22x22/widget-gtk-drawingarea.png | Bin 0 -> 689 bytes plugins/gtk+/icons/22x22/widget-gtk-entry.png | Bin 0 -> 125 bytes .../icons/22x22/widget-gtk-entrybuffer.png | Bin 0 -> 183 bytes .../22x22/widget-gtk-entrycompletion.png | Bin 0 -> 316 bytes .../gtk+/icons/22x22/widget-gtk-eventbox.png | Bin 0 -> 223 bytes .../gtk+/icons/22x22/widget-gtk-expander.png | Bin 0 -> 152 bytes .../22x22/widget-gtk-filechooserbutton.png | Bin 0 -> 198 bytes .../22x22/widget-gtk-filechooserdialog.png | Bin 0 -> 265 bytes .../22x22/widget-gtk-filechooserwidget.png | Bin 0 -> 306 bytes .../icons/22x22/widget-gtk-filefilter.png | Bin 0 -> 464 bytes plugins/gtk+/icons/22x22/widget-gtk-fixed.png | Bin 0 -> 122 bytes .../gtk+/icons/22x22/widget-gtk-flowbox.png | Bin 0 -> 230 bytes .../icons/22x22/widget-gtk-flowboxchild.png | Bin 0 -> 226 bytes .../icons/22x22/widget-gtk-fontbutton.png | Bin 0 -> 175 bytes .../icons/22x22/widget-gtk-fontselection.png | Bin 0 -> 281 bytes .../22x22/widget-gtk-fontselectiondialog.png | Bin 0 -> 321 bytes plugins/gtk+/icons/22x22/widget-gtk-frame.png | Bin 0 -> 143 bytes .../gtk+/icons/22x22/widget-gtk-glarea.png | Bin 0 -> 502 bytes plugins/gtk+/icons/22x22/widget-gtk-grid.png | Bin 0 -> 120 bytes .../gtk+/icons/22x22/widget-gtk-handlebox.png | Bin 0 -> 174 bytes plugins/gtk+/icons/22x22/widget-gtk-hbox.png | Bin 0 -> 114 bytes .../icons/22x22/widget-gtk-hbuttonbox.png | Bin 0 -> 136 bytes .../gtk+/icons/22x22/widget-gtk-headerbar.png | Bin 0 -> 200 bytes .../gtk+/icons/22x22/widget-gtk-hpaned.png | Bin 0 -> 118 bytes .../gtk+/icons/22x22/widget-gtk-hscale.png | Bin 0 -> 172 bytes .../icons/22x22/widget-gtk-hscrollbar.png | Bin 0 -> 155 bytes .../icons/22x22/widget-gtk-hseparator.png | Bin 0 -> 101 bytes .../icons/22x22/widget-gtk-iconfactory.png | Bin 0 -> 444 bytes .../gtk+/icons/22x22/widget-gtk-iconview.png | Bin 0 -> 265 bytes plugins/gtk+/icons/22x22/widget-gtk-image.png | Bin 0 -> 269 bytes .../icons/22x22/widget-gtk-imagemenuitem.png | Bin 0 -> 185 bytes .../gtk+/icons/22x22/widget-gtk-infobar.png | Bin 0 -> 403 bytes .../icons/22x22/widget-gtk-inputdialog.png | Bin 0 -> 214 bytes plugins/gtk+/icons/22x22/widget-gtk-label.png | Bin 0 -> 205 bytes .../gtk+/icons/22x22/widget-gtk-layout.png | Bin 0 -> 186 bytes .../gtk+/icons/22x22/widget-gtk-levelbar.png | Bin 0 -> 268 bytes .../icons/22x22/widget-gtk-linkbutton.png | Bin 0 -> 150 bytes .../gtk+/icons/22x22/widget-gtk-listbox.png | Bin 0 -> 217 bytes .../icons/22x22/widget-gtk-listboxrow.png | Bin 0 -> 191 bytes .../gtk+/icons/22x22/widget-gtk-liststore.png | Bin 0 -> 209 bytes .../icons/22x22/widget-gtk-lockbutton.png | Bin 0 -> 264 bytes plugins/gtk+/icons/22x22/widget-gtk-menu.png | Bin 0 -> 164 bytes .../gtk+/icons/22x22/widget-gtk-menubar.png | Bin 0 -> 183 bytes .../icons/22x22/widget-gtk-menubutton.png | Bin 0 -> 359 bytes .../gtk+/icons/22x22/widget-gtk-menuitem.png | Bin 0 -> 127 bytes .../icons/22x22/widget-gtk-menutoolbutton.png | Bin 0 -> 185 bytes .../icons/22x22/widget-gtk-messagedialog.png | Bin 0 -> 358 bytes .../icons/22x22/widget-gtk-modelbutton.png | Bin 0 -> 248 bytes .../gtk+/icons/22x22/widget-gtk-notebook.png | Bin 0 -> 175 bytes .../22x22/widget-gtk-offscreenwindow.png | Bin 0 -> 239 bytes .../gtk+/icons/22x22/widget-gtk-overlay.png | Bin 0 -> 238 bytes .../22x22/widget-gtk-pagesetupdialog.png | Bin 0 -> 358 bytes plugins/gtk+/icons/22x22/widget-gtk-paned.png | Bin 0 -> 118 bytes .../icons/22x22/widget-gtk-placessidebar.png | Bin 0 -> 235 bytes .../gtk+/icons/22x22/widget-gtk-popover.png | Bin 0 -> 326 bytes .../icons/22x22/widget-gtk-popovermenu.png | Bin 0 -> 327 bytes .../icons/22x22/widget-gtk-printdialog.png | Bin 0 -> 438 bytes .../icons/22x22/widget-gtk-progressbar.png | Bin 0 -> 138 bytes .../icons/22x22/widget-gtk-radioaction.png | Bin 0 -> 340 bytes .../icons/22x22/widget-gtk-radiobutton.png | Bin 0 -> 257 bytes .../icons/22x22/widget-gtk-radiomenuitem.png | Bin 0 -> 323 bytes .../22x22/widget-gtk-radiotoolbutton.png | Bin 0 -> 362 bytes .../icons/22x22/widget-gtk-recentaction.png | Bin 0 -> 346 bytes .../icons/22x22/widget-gtk-recentchooser.png | Bin 0 -> 432 bytes .../22x22/widget-gtk-recentchooserdialog.png | Bin 0 -> 312 bytes .../22x22/widget-gtk-recentchoosermenu.png | Bin 0 -> 340 bytes .../icons/22x22/widget-gtk-recentfilter.png | Bin 0 -> 494 bytes .../icons/22x22/widget-gtk-recentmanager.png | Bin 0 -> 417 bytes .../gtk+/icons/22x22/widget-gtk-revealer.png | Bin 0 -> 221 bytes plugins/gtk+/icons/22x22/widget-gtk-scale.png | Bin 0 -> 164 bytes .../icons/22x22/widget-gtk-scalebutton.png | Bin 0 -> 394 bytes .../gtk+/icons/22x22/widget-gtk-scrollbar.png | Bin 0 -> 344 bytes .../icons/22x22/widget-gtk-scrolledwindow.png | Bin 0 -> 339 bytes .../gtk+/icons/22x22/widget-gtk-searchbar.png | Bin 0 -> 229 bytes .../icons/22x22/widget-gtk-searchentry.png | Bin 0 -> 188 bytes .../gtk+/icons/22x22/widget-gtk-separator.png | Bin 0 -> 107 bytes .../22x22/widget-gtk-separatormenuitem.png | Bin 0 -> 127 bytes .../22x22/widget-gtk-separatortoolitem.png | Bin 0 -> 96 bytes .../gtk+/icons/22x22/widget-gtk-sizegroup.png | Bin 0 -> 254 bytes .../icons/22x22/widget-gtk-spinbutton.png | Bin 0 -> 206 bytes .../gtk+/icons/22x22/widget-gtk-spinner.png | Bin 0 -> 1295 bytes plugins/gtk+/icons/22x22/widget-gtk-stack.png | Bin 0 -> 188 bytes .../icons/22x22/widget-gtk-stacksidebar.png | Bin 0 -> 199 bytes .../icons/22x22/widget-gtk-stackswitcher.png | Bin 0 -> 473 bytes .../gtk+/icons/22x22/widget-gtk-statusbar.png | Bin 0 -> 116 bytes .../icons/22x22/widget-gtk-statusicon.png | Bin 0 -> 307 bytes .../gtk+/icons/22x22/widget-gtk-switch.png | Bin 0 -> 274 bytes plugins/gtk+/icons/22x22/widget-gtk-table.png | Bin 0 -> 120 bytes .../22x22/widget-gtk-tearoffmenuitem.png | Bin 0 -> 201 bytes .../icons/22x22/widget-gtk-textbuffer.png | Bin 0 -> 173 bytes .../gtk+/icons/22x22/widget-gtk-texttag.png | Bin 0 -> 319 bytes .../icons/22x22/widget-gtk-texttagtable.png | Bin 0 -> 387 bytes .../gtk+/icons/22x22/widget-gtk-textview.png | Bin 0 -> 148 bytes .../icons/22x22/widget-gtk-toggleaction.png | Bin 0 -> 249 bytes .../icons/22x22/widget-gtk-togglebutton.png | Bin 0 -> 200 bytes .../22x22/widget-gtk-toggletoolbutton.png | Bin 0 -> 149 bytes .../gtk+/icons/22x22/widget-gtk-toolbar.png | Bin 0 -> 187 bytes .../icons/22x22/widget-gtk-toolbutton.png | Bin 0 -> 149 bytes .../gtk+/icons/22x22/widget-gtk-toolitem.png | Bin 0 -> 132 bytes .../icons/22x22/widget-gtk-toolitemgroup.png | Bin 0 -> 389 bytes .../icons/22x22/widget-gtk-toolpalette.png | Bin 0 -> 374 bytes .../22x22/widget-gtk-treemodelfilter.png | Bin 0 -> 409 bytes .../icons/22x22/widget-gtk-treemodelsort.png | Bin 0 -> 268 bytes .../icons/22x22/widget-gtk-treeselection.png | Bin 0 -> 214 bytes .../gtk+/icons/22x22/widget-gtk-treestore.png | Bin 0 -> 231 bytes .../gtk+/icons/22x22/widget-gtk-treeview.png | Bin 0 -> 158 bytes .../icons/22x22/widget-gtk-treeviewcolumn.png | Bin 0 -> 244 bytes .../gtk+/icons/22x22/widget-gtk-uimanager.png | Bin 0 -> 428 bytes plugins/gtk+/icons/22x22/widget-gtk-vbox.png | Bin 0 -> 142 bytes .../icons/22x22/widget-gtk-vbuttonbox.png | Bin 0 -> 134 bytes .../gtk+/icons/22x22/widget-gtk-viewport.png | Bin 0 -> 186 bytes .../icons/22x22/widget-gtk-volumebutton.png | Bin 0 -> 775 bytes .../gtk+/icons/22x22/widget-gtk-vpaned.png | Bin 0 -> 143 bytes .../gtk+/icons/22x22/widget-gtk-vscale.png | Bin 0 -> 164 bytes .../icons/22x22/widget-gtk-vscrollbar.png | Bin 0 -> 344 bytes .../icons/22x22/widget-gtk-vseparator.png | Bin 0 -> 107 bytes .../gtk+/icons/22x22/widget-gtk-window.png | Bin 0 -> 169 bytes .../icons/22x22/widget-gtk-windowgroup.png | Bin 0 -> 405 bytes plugins/gtk+/icons/meson.build | 165 + plugins/gtk+/meson.build | 249 + plugins/gtk-private/glade-gtk-private.xml | 80 + plugins/meson.build | 38 + plugins/python/glade-python.c | 196 + plugins/python/meson.build | 13 + plugins/webkit2gtk/.gitignore | 6 + plugins/webkit2gtk/glade-webkit2gtk.c | 62 + plugins/webkit2gtk/meson.build | 17 + plugins/webkit2gtk/webkit2gtk.xml | 63 + po/ChangeLog | 1885 +++ po/LINGUAS | 73 + po/POTFILES.in | 250 + po/POTFILES.skip | 8 + po/ar.po | 5659 ++++++++ po/ast.po | 5054 +++++++ po/az.po | 1829 +++ po/bg.po | 5191 +++++++ po/bn.po | 3554 +++++ po/bn_IN.po | 3554 +++++ po/bs.po | 8128 +++++++++++ po/ca.po | 8274 +++++++++++ po/ca@valencia.po | 8498 +++++++++++ po/cs.po | 8230 +++++++++++ po/da.po | 11901 ++++++++++++++++ po/de.po | 8599 +++++++++++ po/el.po | 8498 +++++++++++ po/en@shaw.po | 5553 +++++++ po/en_CA.po | 3801 +++++ po/en_GB.po | 9593 +++++++++++++ po/eo.po | 8434 +++++++++++ po/es.po | 9763 +++++++++++++ po/et.po | 4024 ++++++ po/eu.po | 8252 +++++++++++ po/fi.po | 8602 +++++++++++ po/fr.po | 8383 +++++++++++ po/fur.po | 8366 +++++++++++ po/gl.po | 8441 +++++++++++ po/gu.po | 3940 +++++ po/he.po | 5442 +++++++ po/hi.po | 5605 ++++++++ po/hu.po | 8401 +++++++++++ po/hy.po | 4985 +++++++ po/id.po | 8615 +++++++++++ po/it.po | 8318 +++++++++++ po/ja.po | 7941 +++++++++++ po/kk.po | 8060 +++++++++++ po/km.po | 5011 +++++++ po/ko.po | 8250 +++++++++++ po/lt.po | 8602 +++++++++++ po/lv.po | 8484 +++++++++++ po/mai.po | 5231 +++++++ po/meson.build | 5 + po/mk.po | 3837 +++++ po/ml.po | 8817 ++++++++++++ po/mr.po | 3949 +++++ po/ms.po | 8198 +++++++++++ po/nb.po | 8231 +++++++++++ po/ne.po | 8609 +++++++++++ po/nl.po | 9143 ++++++++++++ po/nn.po | 4808 +++++++ po/oc.po | 8460 +++++++++++ po/or.po | 5391 +++++++ po/pa.po | 3326 +++++ po/pl.po | 8295 +++++++++++ po/pt.po | 8331 +++++++++++ po/pt_BR.po | 9634 +++++++++++++ po/ro.po | 8741 ++++++++++++ po/ru.po | 8473 +++++++++++ po/si.po | 3717 +++++ po/sk.po | 9708 +++++++++++++ po/sl.po | 8642 +++++++++++ po/sq.po | 3987 ++++++ po/sr.po | 8546 +++++++++++ po/sr@latin.po | 8520 +++++++++++ po/sv.po | 8647 +++++++++++ po/ta.po | 3542 +++++ po/te.po | 5519 +++++++ po/th.po | 6077 ++++++++ po/tr.po | 8697 +++++++++++ po/ug.po | 5236 +++++++ po/uk.po | 8266 +++++++++++ po/vi.po | 5494 +++++++ po/zh_CN.po | 8597 +++++++++++ po/zh_HK.po | 7822 ++++++++++ po/zh_TW.po | 8266 +++++++++++ snap/snapcraft.yaml | 63 + src/.gitignore | 11 + src/glade-http.c | 547 + src/glade-http.h | 92 + src/glade-intro.c | 507 + src/glade-intro.h | 70 + src/glade-logo.h | 464 + src/glade-preferences.c | 341 + src/glade-preferences.glade | 403 + src/glade-preferences.h | 60 + src/glade-registration.c | 1100 ++ src/glade-registration.css | 79 + src/glade-registration.glade | 2649 ++++ src/glade-registration.h | 60 + src/glade-resources.gresource.xml | 11 + src/glade-settings.c | 316 + src/glade-settings.h | 46 + src/glade-window.c | 2610 ++++ src/glade-window.css | 130 + src/glade-window.h | 67 + src/glade.glade | 880 ++ src/glade.rc.in | 29 + src/guido.png | Bin 0 -> 47805 bytes src/main.c | 219 + src/meson.build | 61 + src/workaround.h | 86 + tests/add-child.c | 308 + tests/catalogs/gjsplugin.xml | 7 + tests/catalogs/pythonplugin.xml | 8 + tests/create-widgets.c | 124 + tests/meson.build | 64 + tests/modules.c | 40 + tests/modules/gjsplugin.js | 13 + tests/modules/pythonplugin.py | 7 + tests/refcount.c | 51 + tests/toplevel-order-resources.gresource.xml | 11 + tests/toplevel-order.c | 163 + tests/toplevel_order_test.glade | 9 + tests/toplevel_order_test2.glade | 11 + tests/toplevel_order_test3.glade | 9 + tests/toplevel_order_test4.glade | 14 + tests/toplevel_order_test5.glade | 36 + tests/toplevel_order_test6.glade | 117 + 942 files changed, 679865 insertions(+) create mode 100644 AUTHORS create mode 100644 CONTRIBUTING.md create mode 100644 COPYING create mode 100644 COPYING.GPL create mode 100644 COPYING.LGPL create mode 100644 ChangeLog.old.gz create mode 100644 INTERNALS create mode 100644 MAINTAINERS create mode 100644 NEWS create mode 100644 README.md create mode 100644 TODO create mode 100644 data/.gitignore create mode 100644 data/gettext/its/glade-catalog.its create mode 100644 data/gettext/its/glade-catalog.loc create mode 100644 data/icons/deprecated-16x16.png create mode 100644 data/icons/deprecated-22x22.png create mode 100644 data/icons/devhelp.png create mode 100644 data/icons/fixed-bg.png create mode 100644 data/icons/glade.ico create mode 100644 data/icons/hicolor/scalable/apps/org.gnome.Glade.svg create mode 100644 data/icons/hicolor/symbolic/apps/glade-brand-symbolic.svg create mode 100644 data/icons/hicolor/symbolic/apps/org.gnome.Glade-symbolic.svg create mode 100644 data/icons/placeholder.png create mode 100644 data/icons/plus.png create mode 100644 data/meson.build create mode 100644 data/org.gnome.Glade.appdata.xml.in create mode 100644 data/org.gnome.Glade.desktop.in.in create mode 100644 doc/.gitignore create mode 100644 doc/catalogintro.sgml create mode 100644 doc/gladegjs.sgml create mode 100644 doc/gladepython.sgml create mode 100644 doc/gladeui-docs.xml create mode 100644 doc/gladeui-sections.txt create mode 100644 doc/gladeui.types create mode 100644 doc/meson.build create mode 100644 doc/properties.sgml create mode 100644 doc/version.xml.in create mode 100644 doc/widgetclasses.sgml create mode 100644 glade.doap create mode 100644 gladeui/.gitignore create mode 100644 gladeui/atk.png create mode 100644 gladeui/glade-accumulators.c create mode 100644 gladeui/glade-accumulators.h create mode 100644 gladeui/glade-adaptor-chooser-widget.c create mode 100644 gladeui/glade-adaptor-chooser-widget.h create mode 100644 gladeui/glade-adaptor-chooser-widget.ui create mode 100644 gladeui/glade-adaptor-chooser.c create mode 100644 gladeui/glade-adaptor-chooser.h create mode 100644 gladeui/glade-adaptor-chooser.ui create mode 100644 gladeui/glade-app.c create mode 100644 gladeui/glade-app.h create mode 100644 gladeui/glade-base-editor.c create mode 100644 gladeui/glade-base-editor.h create mode 100644 gladeui/glade-base-editor.ui create mode 100644 gladeui/glade-builtins.c create mode 100644 gladeui/glade-builtins.h create mode 100644 gladeui/glade-catalog.c create mode 100644 gladeui/glade-catalog.h create mode 100644 gladeui/glade-cell-renderer-icon.c create mode 100644 gladeui/glade-cell-renderer-icon.h create mode 100644 gladeui/glade-clipboard.c create mode 100644 gladeui/glade-clipboard.h create mode 100644 gladeui/glade-command.c create mode 100644 gladeui/glade-command.h create mode 100644 gladeui/glade-cursor.c create mode 100644 gladeui/glade-cursor.h create mode 100644 gladeui/glade-debug.c create mode 100644 gladeui/glade-debug.h create mode 100644 gladeui/glade-design-layout.c create mode 100644 gladeui/glade-design-layout.css create mode 100644 gladeui/glade-design-layout.h create mode 100644 gladeui/glade-design-private.h create mode 100644 gladeui/glade-design-view.c create mode 100644 gladeui/glade-design-view.h create mode 100644 gladeui/glade-displayable-values.c create mode 100644 gladeui/glade-displayable-values.h create mode 100644 gladeui/glade-dnd.c create mode 100644 gladeui/glade-dnd.h create mode 100644 gladeui/glade-drag.c create mode 100644 gladeui/glade-drag.h create mode 100644 gladeui/glade-editable.c create mode 100644 gladeui/glade-editable.h create mode 100644 gladeui/glade-editor-property.c create mode 100644 gladeui/glade-editor-property.h create mode 100644 gladeui/glade-editor-skeleton.c create mode 100644 gladeui/glade-editor-skeleton.h create mode 100644 gladeui/glade-editor-table.c create mode 100644 gladeui/glade-editor-table.h create mode 100644 gladeui/glade-editor.c create mode 100644 gladeui/glade-editor.h create mode 100644 gladeui/glade-editor.ui create mode 100644 gladeui/glade-id-allocator.c create mode 100644 gladeui/glade-id-allocator.h create mode 100644 gladeui/glade-inspector.c create mode 100644 gladeui/glade-inspector.h create mode 100644 gladeui/glade-marshallers.list create mode 100644 gladeui/glade-name-context.c create mode 100644 gladeui/glade-name-context.h create mode 100644 gladeui/glade-named-icon-chooser-dialog.c create mode 100644 gladeui/glade-named-icon-chooser-dialog.h create mode 100644 gladeui/glade-object-stub.c create mode 100644 gladeui/glade-object-stub.h create mode 100644 gladeui/glade-palette.c create mode 100644 gladeui/glade-palette.h create mode 100644 gladeui/glade-path.h create mode 100644 gladeui/glade-placeholder.c create mode 100644 gladeui/glade-placeholder.h create mode 100644 gladeui/glade-popup.c create mode 100644 gladeui/glade-popup.h create mode 100644 gladeui/glade-preview-template.c create mode 100644 gladeui/glade-preview-template.h create mode 100644 gladeui/glade-preview-tokens.h create mode 100644 gladeui/glade-preview.c create mode 100644 gladeui/glade-preview.h create mode 100644 gladeui/glade-previewer-main.c create mode 100644 gladeui/glade-previewer.c create mode 100644 gladeui/glade-previewer.h create mode 100644 gladeui/glade-previewer.rc.in create mode 100644 gladeui/glade-private.h create mode 100644 gladeui/glade-project-properties.c create mode 100644 gladeui/glade-project-properties.h create mode 100644 gladeui/glade-project-properties.ui create mode 100644 gladeui/glade-project.c create mode 100644 gladeui/glade-project.h create mode 100644 gladeui/glade-property-def.c create mode 100644 gladeui/glade-property-def.h create mode 100644 gladeui/glade-property-label.c create mode 100644 gladeui/glade-property-label.h create mode 100644 gladeui/glade-property-label.ui create mode 100644 gladeui/glade-property-shell.c create mode 100644 gladeui/glade-property-shell.h create mode 100644 gladeui/glade-property.c create mode 100644 gladeui/glade-property.h create mode 100644 gladeui/glade-signal-def.c create mode 100644 gladeui/glade-signal-def.h create mode 100644 gladeui/glade-signal-editor.c create mode 100644 gladeui/glade-signal-editor.h create mode 100644 gladeui/glade-signal-model.c create mode 100644 gladeui/glade-signal-model.h create mode 100644 gladeui/glade-signal.c create mode 100644 gladeui/glade-signal.h create mode 100644 gladeui/glade-template.c create mode 100644 gladeui/glade-tsort.c create mode 100644 gladeui/glade-tsort.h create mode 100644 gladeui/glade-utils.c create mode 100644 gladeui/glade-utils.h create mode 100644 gladeui/glade-widget-action.c create mode 100644 gladeui/glade-widget-action.h create mode 100644 gladeui/glade-widget-adaptor.c create mode 100644 gladeui/glade-widget-adaptor.h create mode 100644 gladeui/glade-widget.c create mode 100644 gladeui/glade-widget.h create mode 100644 gladeui/glade-xml-utils.c create mode 100644 gladeui/glade-xml-utils.h create mode 100644 gladeui/glade.h create mode 100644 gladeui/glade_plugin.def create mode 100644 gladeui/gladeui-enum-types.c.template create mode 100644 gladeui/gladeui-enum-types.h.template create mode 100644 gladeui/gladeui-resources.gresource.xml create mode 100644 gladeui/gladeui.rc.in create mode 100644 gladeui/icon-naming-spec.c create mode 100644 gladeui/meson.build create mode 100644 help/C/figures/main-window.png create mode 100644 help/C/index.docbook create mode 100644 help/C/legal.xml create mode 100644 help/ChangeLog create mode 100644 help/LINGUAS create mode 100644 help/bg/bg.po create mode 100644 help/ca/ca.po create mode 100644 help/cs/cs.po create mode 100644 help/cs/figures/main-window.png create mode 100644 help/da/da.po create mode 100644 help/da/figures/main-window.png create mode 100644 help/de/de.po create mode 100644 help/de/figures/main-window.png create mode 100644 help/el/el.po create mode 100644 help/en_GB/en_GB.po create mode 100644 help/es/es.po create mode 100644 help/es/figures/main-window.png create mode 100644 help/eu/eu.po create mode 100644 help/fr/figures/main-window.png create mode 100644 help/fr/fr.po create mode 100644 help/gl/gl.po create mode 100644 help/hi/hi.po create mode 100644 help/hu/hu.po create mode 100644 help/id/id.po create mode 100644 help/it/it.po create mode 100644 help/ja/ja.po create mode 100644 help/ko/ko.po create mode 100644 help/meson.build create mode 100644 help/oc/oc.po create mode 100644 help/pl/figures/main-window.png create mode 100644 help/pl/pl.po create mode 100644 help/pt_BR/figures/main-window.png create mode 100644 help/pt_BR/pt_BR.po create mode 100644 help/ru/figures/main-window.png create mode 100644 help/ru/ru.po create mode 100644 help/sl/sl.po create mode 100644 help/sv/figures/main-window.png create mode 100644 help/sv/sv.po create mode 100644 help/uk/uk.po create mode 100644 help/zh_CN/zh_CN.po create mode 100644 man/glade-previewer.xml create mode 100644 man/glade.xml create mode 100644 man/meson.build create mode 100644 meson.build create mode 100644 meson_options.txt create mode 100644 meson_post_install.py create mode 100644 org.gnome.Glade.json create mode 100644 plugins/gjs/glade-gjs.c create mode 100644 plugins/gjs/meson.build create mode 100644 plugins/glade-catalog.dtd create mode 100644 plugins/gladeui/glade-glade-editor-skeleton.c create mode 100644 plugins/gladeui/glade-glade-property-shell.c create mode 100644 plugins/gladeui/gladeui.xml create mode 100644 plugins/gladeui/meson.build create mode 100644 plugins/gtk+/.gitignore create mode 100644 plugins/gtk+/glade-about-dialog-editor.c create mode 100644 plugins/gtk+/glade-about-dialog-editor.h create mode 100644 plugins/gtk+/glade-about-dialog-editor.ui create mode 100644 plugins/gtk+/glade-accels.c create mode 100644 plugins/gtk+/glade-accels.h create mode 100644 plugins/gtk+/glade-action-bar-editor.c create mode 100644 plugins/gtk+/glade-action-bar-editor.h create mode 100644 plugins/gtk+/glade-action-bar-editor.ui create mode 100644 plugins/gtk+/glade-action-editor.c create mode 100644 plugins/gtk+/glade-action-editor.h create mode 100644 plugins/gtk+/glade-action-editor.ui create mode 100644 plugins/gtk+/glade-activatable-editor.c create mode 100644 plugins/gtk+/glade-activatable-editor.h create mode 100644 plugins/gtk+/glade-activatable-editor.ui create mode 100644 plugins/gtk+/glade-app-chooser-button-editor.c create mode 100644 plugins/gtk+/glade-app-chooser-button-editor.h create mode 100644 plugins/gtk+/glade-app-chooser-button-editor.ui create mode 100644 plugins/gtk+/glade-app-chooser-widget-editor.c create mode 100644 plugins/gtk+/glade-app-chooser-widget-editor.h create mode 100644 plugins/gtk+/glade-app-chooser-widget-editor.ui create mode 100644 plugins/gtk+/glade-arrow-editor.c create mode 100644 plugins/gtk+/glade-arrow-editor.h create mode 100644 plugins/gtk+/glade-arrow-editor.ui create mode 100644 plugins/gtk+/glade-attributes.c create mode 100644 plugins/gtk+/glade-attributes.h create mode 100644 plugins/gtk+/glade-box-editor.c create mode 100644 plugins/gtk+/glade-box-editor.h create mode 100644 plugins/gtk+/glade-box-editor.ui create mode 100644 plugins/gtk+/glade-button-editor.c create mode 100644 plugins/gtk+/glade-button-editor.h create mode 100644 plugins/gtk+/glade-button-editor.ui create mode 100644 plugins/gtk+/glade-cell-renderer-editor.c create mode 100644 plugins/gtk+/glade-cell-renderer-editor.h create mode 100644 plugins/gtk+/glade-column-types.c create mode 100644 plugins/gtk+/glade-column-types.h create mode 100644 plugins/gtk+/glade-combo-box-editor.c create mode 100644 plugins/gtk+/glade-combo-box-editor.h create mode 100644 plugins/gtk+/glade-combo-box-editor.ui create mode 100644 plugins/gtk+/glade-combo-box-text-editor.c create mode 100644 plugins/gtk+/glade-combo-box-text-editor.h create mode 100644 plugins/gtk+/glade-combo-box-text-editor.ui create mode 100644 plugins/gtk+/glade-entry-editor.c create mode 100644 plugins/gtk+/glade-entry-editor.h create mode 100644 plugins/gtk+/glade-entry-editor.ui create mode 100644 plugins/gtk+/glade-eprop-enum-int.c create mode 100644 plugins/gtk+/glade-eprop-enum-int.h create mode 100644 plugins/gtk+/glade-file-chooser-button-editor.c create mode 100644 plugins/gtk+/glade-file-chooser-button-editor.h create mode 100644 plugins/gtk+/glade-file-chooser-button-editor.ui create mode 100644 plugins/gtk+/glade-file-chooser-dialog-editor.c create mode 100644 plugins/gtk+/glade-file-chooser-dialog-editor.h create mode 100644 plugins/gtk+/glade-file-chooser-dialog-editor.ui create mode 100644 plugins/gtk+/glade-file-chooser-editor.c create mode 100644 plugins/gtk+/glade-file-chooser-editor.h create mode 100644 plugins/gtk+/glade-file-chooser-editor.ui create mode 100644 plugins/gtk+/glade-file-chooser-widget-editor.c create mode 100644 plugins/gtk+/glade-file-chooser-widget-editor.h create mode 100644 plugins/gtk+/glade-file-chooser-widget-editor.ui create mode 100644 plugins/gtk+/glade-fixed.c create mode 100644 plugins/gtk+/glade-fixed.h create mode 100644 plugins/gtk+/glade-font-button-editor.c create mode 100644 plugins/gtk+/glade-font-button-editor.h create mode 100644 plugins/gtk+/glade-font-button-editor.ui create mode 100644 plugins/gtk+/glade-font-chooser-dialog-editor.c create mode 100644 plugins/gtk+/glade-font-chooser-dialog-editor.h create mode 100644 plugins/gtk+/glade-font-chooser-dialog-editor.ui create mode 100644 plugins/gtk+/glade-font-chooser-editor.c create mode 100644 plugins/gtk+/glade-font-chooser-editor.h create mode 100644 plugins/gtk+/glade-font-chooser-editor.ui create mode 100644 plugins/gtk+/glade-font-chooser-widget-editor.c create mode 100644 plugins/gtk+/glade-font-chooser-widget-editor.h create mode 100644 plugins/gtk+/glade-font-chooser-widget-editor.ui create mode 100644 plugins/gtk+/glade-grid-editor.c create mode 100644 plugins/gtk+/glade-grid-editor.h create mode 100644 plugins/gtk+/glade-grid-editor.ui create mode 100644 plugins/gtk+/glade-gtk-about-dialog.c create mode 100644 plugins/gtk+/glade-gtk-action-bar.c create mode 100644 plugins/gtk+/glade-gtk-action-group.c create mode 100644 plugins/gtk+/glade-gtk-action-widgets.c create mode 100644 plugins/gtk+/glade-gtk-action-widgets.h create mode 100644 plugins/gtk+/glade-gtk-action.c create mode 100644 plugins/gtk+/glade-gtk-adjustment.c create mode 100644 plugins/gtk+/glade-gtk-app-chooser-button.c create mode 100644 plugins/gtk+/glade-gtk-app-chooser-widget.c create mode 100644 plugins/gtk+/glade-gtk-arrow.c create mode 100644 plugins/gtk+/glade-gtk-assistant.c create mode 100644 plugins/gtk+/glade-gtk-bin.c create mode 100644 plugins/gtk+/glade-gtk-box.c create mode 100644 plugins/gtk+/glade-gtk-button.c create mode 100644 plugins/gtk+/glade-gtk-button.h create mode 100644 plugins/gtk+/glade-gtk-cell-layout.c create mode 100644 plugins/gtk+/glade-gtk-cell-layout.h create mode 100644 plugins/gtk+/glade-gtk-cell-renderer.c create mode 100644 plugins/gtk+/glade-gtk-cell-renderer.h create mode 100644 plugins/gtk+/glade-gtk-combo-box-text.c create mode 100644 plugins/gtk+/glade-gtk-combo-box.c create mode 100644 plugins/gtk+/glade-gtk-container.c create mode 100644 plugins/gtk+/glade-gtk-dialog.c create mode 100644 plugins/gtk+/glade-gtk-dialog.h create mode 100644 plugins/gtk+/glade-gtk-entry-buffer.c create mode 100644 plugins/gtk+/glade-gtk-entry.c create mode 100644 plugins/gtk+/glade-gtk-expander.c create mode 100644 plugins/gtk+/glade-gtk-file-chooser-widget.c create mode 100644 plugins/gtk+/glade-gtk-fixed-layout.c create mode 100644 plugins/gtk+/glade-gtk-flow-box.c create mode 100644 plugins/gtk+/glade-gtk-font-chooser-widget.c create mode 100644 plugins/gtk+/glade-gtk-frame.c create mode 100644 plugins/gtk+/glade-gtk-frame.h create mode 100644 plugins/gtk+/glade-gtk-grid.c create mode 100644 plugins/gtk+/glade-gtk-header-bar.c create mode 100644 plugins/gtk+/glade-gtk-icon-factory.c create mode 100644 plugins/gtk+/glade-gtk-icon-view.c create mode 100644 plugins/gtk+/glade-gtk-image-menu-item.c create mode 100644 plugins/gtk+/glade-gtk-image.c create mode 100644 plugins/gtk+/glade-gtk-image.h create mode 100644 plugins/gtk+/glade-gtk-info-bar.c create mode 100644 plugins/gtk+/glade-gtk-label.c create mode 100644 plugins/gtk+/glade-gtk-level-bar.c create mode 100644 plugins/gtk+/glade-gtk-list-box.c create mode 100644 plugins/gtk+/glade-gtk-list-store.c create mode 100644 plugins/gtk+/glade-gtk-marshallers.list create mode 100644 plugins/gtk+/glade-gtk-menu-bar.c create mode 100644 plugins/gtk+/glade-gtk-menu-item.c create mode 100644 plugins/gtk+/glade-gtk-menu-shell.c create mode 100644 plugins/gtk+/glade-gtk-menu-shell.h create mode 100644 plugins/gtk+/glade-gtk-menu-tool-button.c create mode 100644 plugins/gtk+/glade-gtk-menu.c create mode 100644 plugins/gtk+/glade-gtk-message-dialog.c create mode 100644 plugins/gtk+/glade-gtk-model-button.c create mode 100644 plugins/gtk+/glade-gtk-notebook.c create mode 100644 plugins/gtk+/glade-gtk-notebook.h create mode 100644 plugins/gtk+/glade-gtk-overlay.c create mode 100644 plugins/gtk+/glade-gtk-paned.c create mode 100644 plugins/gtk+/glade-gtk-popover-menu.c create mode 100644 plugins/gtk+/glade-gtk-popover.c create mode 100644 plugins/gtk+/glade-gtk-progress-bar.c create mode 100644 plugins/gtk+/glade-gtk-radio-button.c create mode 100644 plugins/gtk+/glade-gtk-radio-menu-item.c create mode 100644 plugins/gtk+/glade-gtk-recent-chooser-menu.c create mode 100644 plugins/gtk+/glade-gtk-recent-chooser-widget.c create mode 100644 plugins/gtk+/glade-gtk-recent-file-filter.c create mode 100644 plugins/gtk+/glade-gtk-resources.gresource.xml create mode 100644 plugins/gtk+/glade-gtk-revealer.c create mode 100644 plugins/gtk+/glade-gtk-scale.c create mode 100644 plugins/gtk+/glade-gtk-scrollbar.c create mode 100644 plugins/gtk+/glade-gtk-scrolled-window.c create mode 100644 plugins/gtk+/glade-gtk-searchbar.c create mode 100644 plugins/gtk+/glade-gtk-size-group.c create mode 100644 plugins/gtk+/glade-gtk-spin-button.c create mode 100644 plugins/gtk+/glade-gtk-stack-switcher.c create mode 100644 plugins/gtk+/glade-gtk-stack.c create mode 100644 plugins/gtk+/glade-gtk-switch.c create mode 100644 plugins/gtk+/glade-gtk-table.c create mode 100644 plugins/gtk+/glade-gtk-text-buffer.c create mode 100644 plugins/gtk+/glade-gtk-text-tag-table.c create mode 100644 plugins/gtk+/glade-gtk-text-view.c create mode 100644 plugins/gtk+/glade-gtk-tool-button.c create mode 100644 plugins/gtk+/glade-gtk-tool-item-group.c create mode 100644 plugins/gtk+/glade-gtk-tool-item.c create mode 100644 plugins/gtk+/glade-gtk-tool-palette.c create mode 100644 plugins/gtk+/glade-gtk-toolbar.c create mode 100644 plugins/gtk+/glade-gtk-tree-view.c create mode 100644 plugins/gtk+/glade-gtk-tree-view.h create mode 100644 plugins/gtk+/glade-gtk-viewport.c create mode 100644 plugins/gtk+/glade-gtk-widget.c create mode 100644 plugins/gtk+/glade-gtk-window.c create mode 100644 plugins/gtk+/glade-gtk.c create mode 100644 plugins/gtk+/glade-gtk.h create mode 100644 plugins/gtk+/glade-header-bar-editor.c create mode 100644 plugins/gtk+/glade-header-bar-editor.h create mode 100644 plugins/gtk+/glade-header-bar-editor.ui create mode 100644 plugins/gtk+/glade-icon-factory-editor.c create mode 100644 plugins/gtk+/glade-icon-factory-editor.h create mode 100644 plugins/gtk+/glade-icon-sources.c create mode 100644 plugins/gtk+/glade-icon-sources.h create mode 100644 plugins/gtk+/glade-icon-view-editor.c create mode 100644 plugins/gtk+/glade-icon-view-editor.h create mode 100644 plugins/gtk+/glade-icon-view-editor.ui create mode 100644 plugins/gtk+/glade-image-editor.c create mode 100644 plugins/gtk+/glade-image-editor.h create mode 100644 plugins/gtk+/glade-image-editor.ui create mode 100644 plugins/gtk+/glade-image-item-editor.c create mode 100644 plugins/gtk+/glade-image-item-editor.h create mode 100644 plugins/gtk+/glade-label-editor.c create mode 100644 plugins/gtk+/glade-label-editor.h create mode 100644 plugins/gtk+/glade-label-editor.ui create mode 100644 plugins/gtk+/glade-layout-editor.c create mode 100644 plugins/gtk+/glade-layout-editor.h create mode 100644 plugins/gtk+/glade-layout-editor.ui create mode 100644 plugins/gtk+/glade-level-bar-editor.c create mode 100644 plugins/gtk+/glade-level-bar-editor.h create mode 100644 plugins/gtk+/glade-level-bar-editor.ui create mode 100644 plugins/gtk+/glade-message-dialog-editor.c create mode 100644 plugins/gtk+/glade-message-dialog-editor.h create mode 100644 plugins/gtk+/glade-message-dialog-editor.ui create mode 100644 plugins/gtk+/glade-misc-editor.c create mode 100644 plugins/gtk+/glade-misc-editor.h create mode 100644 plugins/gtk+/glade-misc-editor.ui create mode 100644 plugins/gtk+/glade-model-button-editor.c create mode 100644 plugins/gtk+/glade-model-button-editor.h create mode 100644 plugins/gtk+/glade-model-button-editor.ui create mode 100644 plugins/gtk+/glade-model-data.c create mode 100644 plugins/gtk+/glade-model-data.h create mode 100644 plugins/gtk+/glade-notebook-editor.c create mode 100644 plugins/gtk+/glade-notebook-editor.h create mode 100644 plugins/gtk+/glade-notebook-editor.ui create mode 100644 plugins/gtk+/glade-popover-editor.c create mode 100644 plugins/gtk+/glade-popover-editor.h create mode 100644 plugins/gtk+/glade-popover-editor.ui create mode 100644 plugins/gtk+/glade-popover-menu-editor.c create mode 100644 plugins/gtk+/glade-popover-menu-editor.h create mode 100644 plugins/gtk+/glade-popover-menu-editor.ui create mode 100644 plugins/gtk+/glade-progress-bar-editor.c create mode 100644 plugins/gtk+/glade-progress-bar-editor.h create mode 100644 plugins/gtk+/glade-progress-bar-editor.ui create mode 100644 plugins/gtk+/glade-real-tree-view-editor.c create mode 100644 plugins/gtk+/glade-real-tree-view-editor.h create mode 100644 plugins/gtk+/glade-real-tree-view-editor.ui create mode 100644 plugins/gtk+/glade-recent-action-editor.c create mode 100644 plugins/gtk+/glade-recent-action-editor.h create mode 100644 plugins/gtk+/glade-recent-action-editor.ui create mode 100644 plugins/gtk+/glade-recent-chooser-dialog-editor.c create mode 100644 plugins/gtk+/glade-recent-chooser-dialog-editor.h create mode 100644 plugins/gtk+/glade-recent-chooser-dialog-editor.ui create mode 100644 plugins/gtk+/glade-recent-chooser-editor.c create mode 100644 plugins/gtk+/glade-recent-chooser-editor.h create mode 100644 plugins/gtk+/glade-recent-chooser-editor.ui create mode 100644 plugins/gtk+/glade-recent-chooser-menu-editor.c create mode 100644 plugins/gtk+/glade-recent-chooser-menu-editor.h create mode 100644 plugins/gtk+/glade-recent-chooser-menu-editor.ui create mode 100644 plugins/gtk+/glade-recent-chooser-widget-editor.c create mode 100644 plugins/gtk+/glade-recent-chooser-widget-editor.h create mode 100644 plugins/gtk+/glade-recent-chooser-widget-editor.ui create mode 100644 plugins/gtk+/glade-scale-button-editor.c create mode 100644 plugins/gtk+/glade-scale-button-editor.h create mode 100644 plugins/gtk+/glade-scale-button-editor.ui create mode 100644 plugins/gtk+/glade-scale-editor.c create mode 100644 plugins/gtk+/glade-scale-editor.h create mode 100644 plugins/gtk+/glade-scale-editor.ui create mode 100644 plugins/gtk+/glade-scrollable-editor.c create mode 100644 plugins/gtk+/glade-scrollable-editor.h create mode 100644 plugins/gtk+/glade-scrollable-editor.ui create mode 100644 plugins/gtk+/glade-scrollbar-editor.c create mode 100644 plugins/gtk+/glade-scrollbar-editor.h create mode 100644 plugins/gtk+/glade-scrollbar-editor.ui create mode 100644 plugins/gtk+/glade-scrolled-window-editor.c create mode 100644 plugins/gtk+/glade-scrolled-window-editor.h create mode 100644 plugins/gtk+/glade-scrolled-window-editor.ui create mode 100644 plugins/gtk+/glade-spin-button-editor.c create mode 100644 plugins/gtk+/glade-spin-button-editor.h create mode 100644 plugins/gtk+/glade-spin-button-editor.ui create mode 100644 plugins/gtk+/glade-stack-editor.c create mode 100644 plugins/gtk+/glade-stack-editor.h create mode 100644 plugins/gtk+/glade-stack-editor.ui create mode 100644 plugins/gtk+/glade-stack-switcher-editor.c create mode 100644 plugins/gtk+/glade-stack-switcher-editor.h create mode 100644 plugins/gtk+/glade-stack-switcher-editor.ui create mode 100644 plugins/gtk+/glade-store-editor.c create mode 100644 plugins/gtk+/glade-store-editor.h create mode 100644 plugins/gtk+/glade-string-list.c create mode 100644 plugins/gtk+/glade-string-list.h create mode 100644 plugins/gtk+/glade-text-view-editor.c create mode 100644 plugins/gtk+/glade-text-view-editor.h create mode 100644 plugins/gtk+/glade-text-view-editor.ui create mode 100644 plugins/gtk+/glade-tool-button-editor.c create mode 100644 plugins/gtk+/glade-tool-button-editor.h create mode 100644 plugins/gtk+/glade-tool-button-editor.ui create mode 100644 plugins/gtk+/glade-tool-item-group-editor.c create mode 100644 plugins/gtk+/glade-tool-item-group-editor.h create mode 100644 plugins/gtk+/glade-tool-palette-editor.c create mode 100644 plugins/gtk+/glade-tool-palette-editor.h create mode 100644 plugins/gtk+/glade-tool-palette-editor.ui create mode 100644 plugins/gtk+/glade-treeview-editor.c create mode 100644 plugins/gtk+/glade-treeview-editor.h create mode 100644 plugins/gtk+/glade-viewport-editor.c create mode 100644 plugins/gtk+/glade-viewport-editor.h create mode 100644 plugins/gtk+/glade-viewport-editor.ui create mode 100644 plugins/gtk+/glade-widget-editor.c create mode 100644 plugins/gtk+/glade-widget-editor.h create mode 100644 plugins/gtk+/glade-widget-editor.ui create mode 100644 plugins/gtk+/glade-window-editor.c create mode 100644 plugins/gtk+/glade-window-editor.h create mode 100644 plugins/gtk+/glade-window-editor.ui create mode 100644 plugins/gtk+/gtk+.xml create mode 100644 plugins/gtk+/gtkunixprint.xml create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-aboutdialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-accelgroup.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-accellabel.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-action.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-actionbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-actiongroup.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-adjustment.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-alignment.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-appchooserbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-appchooserdialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-appchooserwidget.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-arrow.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-aspectframe.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-assistant.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-box.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-button.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-buttonbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-calendar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellareabox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrendereraccel.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrenderercombo.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrendererpixbuf.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrendererprogress.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrendererspin.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrendererspinner.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrenderertext.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-cellrenderertoggle.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-checkbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-checkmenuitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-colorbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-colorselection.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-colorselectiondialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-combobox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-comboboxtext.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-custom.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-default.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-dialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-drawingarea.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-entry.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-entrybuffer.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-entrycompletion.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-eventbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-expander.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-filechooserbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-filechooserdialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-filechooserwidget.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-filefilter.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-fixed.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-flowbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-flowboxchild.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-fontbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-fontselection.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-fontselectiondialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-frame.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-glarea.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-grid.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-gtktextview.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-handlebox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-hbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-hbuttonbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-headerbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-hpaned.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-hscale.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-hscrollbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-hseparator.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-iconfactory.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-iconview.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-image.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-imagemenuitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-infobar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-inputdialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-label.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-layout.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-levelbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-linkbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-listbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-listboxrow.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-liststore.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-lockbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-menu.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-menubar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-menubutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-menuitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-menutoolbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-messagedialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-modelbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-notebook.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-offscreenwindow.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-overlay.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-pagesetupdialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-paned.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-placessidebar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-popover.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-popovermenu.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-printdialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-progressbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-radioaction.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-radiobutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-radiomenuitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-radiotoolbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-recentaction.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-recentchooser.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-recentchooserdialog.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-recentchoosermenu.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-recentfilter.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-recentmanager.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-revealer.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-scale.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-scalebutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-scrollbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-scrolledwindow.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-searchbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-searchentry.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-separator.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-separatormenuitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-separatortoolitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-sizegroup.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-spinbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-spinner.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-stack.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-stacksidebar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-stackswitcher.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-statusbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-statusicon.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-switch.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-table.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-tearoffmenuitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-textbuffer.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-texttag.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-texttagtable.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-textview.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-toggleaction.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-togglebutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-toggletoolbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-toolbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-toolbutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-toolitem.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-toolitemgroup.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-toolpalette.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-treemodelfilter.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-treemodelsort.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-treeselection.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-treestore.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-treeview.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-treeviewcolumn.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-uimanager.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-vbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-vbuttonbox.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-viewport.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-volumebutton.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-vpaned.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-vscale.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-vscrollbar.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-vseparator.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-window.png create mode 100644 plugins/gtk+/icons/16x16/widget-gtk-windowgroup.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-aboutdialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-accelgroup.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-accellabel.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-action.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-actionbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-actiongroup.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-adjustment.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-alignment.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-appchooserbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-appchooserdialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-appchooserwidget.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-arrow.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-aspectframe.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-assistant.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-box.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-button.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-buttonbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-calendar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellareabox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrendereraccel.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrenderercombo.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrendererpixbuf.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrendererprogress.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrendererspin.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrendererspinner.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrenderertext.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-cellrenderertoggle.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-checkbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-checkmenuitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-colorbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-colorselection.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-colorselectiondialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-combobox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-comboboxtext.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-custom.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-default.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-dialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-drawingarea.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-entry.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-entrybuffer.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-entrycompletion.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-eventbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-expander.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-filechooserbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-filechooserdialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-filechooserwidget.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-filefilter.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-fixed.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-flowbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-flowboxchild.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-fontbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-fontselection.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-fontselectiondialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-frame.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-glarea.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-grid.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-handlebox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-hbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-hbuttonbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-headerbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-hpaned.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-hscale.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-hscrollbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-hseparator.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-iconfactory.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-iconview.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-image.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-imagemenuitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-infobar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-inputdialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-label.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-layout.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-levelbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-linkbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-listbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-listboxrow.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-liststore.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-lockbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-menu.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-menubar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-menubutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-menuitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-menutoolbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-messagedialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-modelbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-notebook.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-offscreenwindow.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-overlay.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-pagesetupdialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-paned.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-placessidebar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-popover.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-popovermenu.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-printdialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-progressbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-radioaction.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-radiobutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-radiomenuitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-radiotoolbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-recentaction.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-recentchooser.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-recentchooserdialog.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-recentchoosermenu.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-recentfilter.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-recentmanager.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-revealer.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-scale.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-scalebutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-scrollbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-scrolledwindow.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-searchbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-searchentry.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-separator.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-separatormenuitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-separatortoolitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-sizegroup.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-spinbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-spinner.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-stack.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-stacksidebar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-stackswitcher.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-statusbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-statusicon.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-switch.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-table.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-tearoffmenuitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-textbuffer.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-texttag.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-texttagtable.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-textview.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-toggleaction.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-togglebutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-toggletoolbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-toolbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-toolbutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-toolitem.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-toolitemgroup.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-toolpalette.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-treemodelfilter.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-treemodelsort.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-treeselection.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-treestore.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-treeview.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-treeviewcolumn.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-uimanager.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-vbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-vbuttonbox.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-viewport.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-volumebutton.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-vpaned.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-vscale.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-vscrollbar.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-vseparator.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-window.png create mode 100644 plugins/gtk+/icons/22x22/widget-gtk-windowgroup.png create mode 100644 plugins/gtk+/icons/meson.build create mode 100644 plugins/gtk+/meson.build create mode 100644 plugins/gtk-private/glade-gtk-private.xml create mode 100644 plugins/meson.build create mode 100644 plugins/python/glade-python.c create mode 100644 plugins/python/meson.build create mode 100644 plugins/webkit2gtk/.gitignore create mode 100644 plugins/webkit2gtk/glade-webkit2gtk.c create mode 100644 plugins/webkit2gtk/meson.build create mode 100644 plugins/webkit2gtk/webkit2gtk.xml create mode 100644 po/ChangeLog create mode 100644 po/LINGUAS create mode 100644 po/POTFILES.in create mode 100644 po/POTFILES.skip create mode 100644 po/ar.po create mode 100644 po/ast.po create mode 100644 po/az.po create mode 100644 po/bg.po create mode 100644 po/bn.po create mode 100644 po/bn_IN.po create mode 100644 po/bs.po create mode 100644 po/ca.po create mode 100644 po/ca@valencia.po create mode 100644 po/cs.po create mode 100644 po/da.po create mode 100644 po/de.po create mode 100644 po/el.po create mode 100644 po/en@shaw.po create mode 100644 po/en_CA.po create mode 100644 po/en_GB.po create mode 100644 po/eo.po create mode 100644 po/es.po create mode 100644 po/et.po create mode 100644 po/eu.po create mode 100644 po/fi.po create mode 100644 po/fr.po create mode 100644 po/fur.po create mode 100644 po/gl.po create mode 100644 po/gu.po create mode 100644 po/he.po create mode 100644 po/hi.po create mode 100644 po/hu.po create mode 100644 po/hy.po create mode 100644 po/id.po create mode 100644 po/it.po create mode 100644 po/ja.po create mode 100644 po/kk.po create mode 100644 po/km.po create mode 100644 po/ko.po create mode 100644 po/lt.po create mode 100644 po/lv.po create mode 100644 po/mai.po create mode 100644 po/meson.build create mode 100644 po/mk.po create mode 100644 po/ml.po create mode 100644 po/mr.po create mode 100644 po/ms.po create mode 100644 po/nb.po create mode 100644 po/ne.po create mode 100644 po/nl.po create mode 100644 po/nn.po create mode 100644 po/oc.po create mode 100644 po/or.po create mode 100644 po/pa.po create mode 100644 po/pl.po create mode 100644 po/pt.po create mode 100644 po/pt_BR.po create mode 100644 po/ro.po create mode 100644 po/ru.po create mode 100644 po/si.po create mode 100644 po/sk.po create mode 100644 po/sl.po create mode 100644 po/sq.po create mode 100644 po/sr.po create mode 100644 po/sr@latin.po create mode 100644 po/sv.po create mode 100644 po/ta.po create mode 100644 po/te.po create mode 100644 po/th.po create mode 100644 po/tr.po create mode 100644 po/ug.po create mode 100644 po/uk.po create mode 100644 po/vi.po create mode 100644 po/zh_CN.po create mode 100644 po/zh_HK.po create mode 100644 po/zh_TW.po create mode 100644 snap/snapcraft.yaml create mode 100644 src/.gitignore create mode 100644 src/glade-http.c create mode 100644 src/glade-http.h create mode 100644 src/glade-intro.c create mode 100644 src/glade-intro.h create mode 100644 src/glade-logo.h create mode 100644 src/glade-preferences.c create mode 100644 src/glade-preferences.glade create mode 100644 src/glade-preferences.h create mode 100644 src/glade-registration.c create mode 100644 src/glade-registration.css create mode 100644 src/glade-registration.glade create mode 100644 src/glade-registration.h create mode 100644 src/glade-resources.gresource.xml create mode 100644 src/glade-settings.c create mode 100644 src/glade-settings.h create mode 100644 src/glade-window.c create mode 100644 src/glade-window.css create mode 100644 src/glade-window.h create mode 100644 src/glade.glade create mode 100644 src/glade.rc.in create mode 100644 src/guido.png create mode 100644 src/main.c create mode 100644 src/meson.build create mode 100644 src/workaround.h create mode 100644 tests/add-child.c create mode 100644 tests/catalogs/gjsplugin.xml create mode 100644 tests/catalogs/pythonplugin.xml create mode 100644 tests/create-widgets.c create mode 100644 tests/meson.build create mode 100644 tests/modules.c create mode 100644 tests/modules/gjsplugin.js create mode 100644 tests/modules/pythonplugin.py create mode 100644 tests/refcount.c create mode 100644 tests/toplevel-order-resources.gresource.xml create mode 100644 tests/toplevel-order.c create mode 100644 tests/toplevel_order_test.glade create mode 100644 tests/toplevel_order_test2.glade create mode 100644 tests/toplevel_order_test3.glade create mode 100644 tests/toplevel_order_test4.glade create mode 100644 tests/toplevel_order_test5.glade create mode 100644 tests/toplevel_order_test6.glade diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..5378499 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,9 @@ +Chema Celorio +Joaquin Cuenca Abela +Paolo Borelli +Archit Baweja +Shane Butler +Tristan Van Berkom +Ivan Wong +Juan Pablo Ugarte +Vincent Geddes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..1f716f5 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,70 @@ +Glade official git repository is hosted by the GNOME foundation at +gitlab.gnome.org + +Mailing List +~~~~~~~~~~~~ +Glade discussion takes place on glade-devel-list@gnome.org + +To subscribe or to consult archives visit + https://mail.gnome.org/mailman/listinfo/glade-devel-list + + +GitLab +~~~~~~~~ +Glade bugs are tracked at + + https://gitlab.gnome.org/GNOME/glade + + +GIT +~~~ +You can browse the source code at https://gitlab.gnome.org/GNOME/glade +To check out a copy of Glade you can use the following command: + + git clone https://gitlab.gnome.org/GNOME/glade.git + +Patches +~~~~~~~ +Patches must be in the unified format (diff -u) and must include a +ChangeLog entry. Please send all patches to bugzilla. + +It is better to use git format-patch command + +git format-patch HEAD^ + +Coding Style +~~~~~~~~~~~~ +Code in Glade should follow the GNU style of GNOME Programming Guidelines +(https://developer.gnome.org/programming-guidelines/stable/c-coding-style.html.en), +basically this means being consistent with the surrounding code. +The only exception is that we prefer having braces always on a new line +e.g.: + +if (...) + { + ... + } + +Note however that a lot of the current codebase still uses the following +style: + +if (...) { + ... +} + +Over time we'll migrate to the preferred form. + +Naming conventions: +- function names should be lowercase and prefixed with the + file name (or, if the function is static and the name too long, + with an abbreviation), e.g: + glade_project_window_my_function () + gpw_my_loooooooooong_named_fuction () +- variable names should be lowercase and be short but self explanatory; + if you need more than one word use an underscore, e.g: + my_variable + +Also try to order your functions so that prototypes are not needed. + + + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..f0b9d59 --- /dev/null +++ b/COPYING @@ -0,0 +1,5 @@ +This package is licensed under the GNU LGPL. Some code is still only available under GNU GPL. +Files that are not yet completely licenced under GNU LGPL are still marked as being licended as GNU GPL. + +Please see COPYING.LGPL for the full text of the LGPL license and the COPYING.GPL +for the full text of the GPL text respectively. diff --git a/COPYING.GPL b/COPYING.GPL new file mode 100644 index 0000000..d8cf7d4 --- /dev/null +++ b/COPYING.GPL @@ -0,0 +1,280 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/COPYING.LGPL b/COPYING.LGPL new file mode 100644 index 0000000..643ab66 --- /dev/null +++ b/COPYING.LGPL @@ -0,0 +1,437 @@ + GNU LIBRARY GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1991 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the library GPL. It is + numbered 2 because it goes with version 2 of the ordinary GPL.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Library General Public License, applies to some +specially designated Free Software Foundation software, and to any +other libraries whose authors decide to use it. You can use it for +your libraries, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if +you distribute copies of the library, or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link a program with the library, you must provide +complete object files to the recipients so that they can relink them +with the library, after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + Our method of protecting your rights has two steps: (1) copyright +the library, and (2) offer you this license which gives you legal +permission to copy, distribute and/or modify the library. + + Also, for each distributor's protection, we want to make certain +that everyone understands that there is no warranty for this free +library. If the library is modified by someone else and passed on, we +want its recipients to know that what they have is not the original +version, so that any problems introduced by others will not reflect on +the original authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that companies distributing free +software will individually obtain patent licenses, thus in effect +transforming the program into proprietary software. To prevent this, +we have made it clear that any patent must be licensed for everyone's +free use or not licensed at all. + + Most GNU software, including some libraries, is covered by the ordinary +GNU General Public License, which was designed for utility programs. This +license, the GNU Library General Public License, applies to certain +designated libraries. This license is quite different from the ordinary +one; be sure to read it in full, and don't assume that anything in it is +the same as in the ordinary license. + + The reason we have a separate public license for some libraries is that +they blur the distinction we usually make between modifying or adding to a +program and simply using it. Linking a program with a library, without +changing the library, is in some sense simply using the library, and is +analogous to running a utility program or application program. However, in +a textual and legal sense, the linked executable is a combined work, a +derivative of the original library, and the ordinary General Public License +treats it as such. + + Because of this blurred distinction, using the ordinary General +Public License for libraries did not effectively promote software +sharing, because most developers did not use the libraries. We +concluded that weaker conditions might promote sharing better. + + However, unrestricted linking of non-free programs would deprive the +users of those programs of all benefit from the free status of the +libraries themselves. This Library General Public License is intended to +permit developers of non-free programs to use free libraries, while +preserving your freedom as a user of such programs to change the free +libraries that are incorporated in them. (We have not seen how to achieve +this as regards changes in header files, but we have achieved it as regards +changes in the actual functions of the Library.) The hope is that this +will lead to faster development of free libraries. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, while the latter only +works together with the library. + + Note that it is possible for a library to be covered by the ordinary +General Public License rather than by this special one. + + GNU LIBRARY GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library which +contains a notice placed by the copyright holder or other authorized +party saying it may be distributed under the terms of this Library +General Public License (also called "this License"). Each licensee is +addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also compile or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + c) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + d) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the source code distributed need not include anything that is normally +distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Library General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS diff --git a/ChangeLog.old.gz b/ChangeLog.old.gz new file mode 100644 index 0000000000000000000000000000000000000000..db5c6596124f9e85e203d89cdf9abff1544acb73 GIT binary patch literal 149641 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%0AjZbg6?l`PL8N5qEY$71V1>jalEnhm% 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 o2dcv4Cil+G(R}>QC-lzu3-FeXT^P7Dj

GtkBuilder element) + Because GMenu is in Glib library it can not implement GtkBuildable iface which + is in GTK+ this lead to implementing GMenu object construction in GtkBuilder + using a custom element + Ideally we should move GtkBuilder and GtkBuildable to Glib and rename them + GBuilder and GBuildable so that we can implemet GBuildable in GMenu object + A way to avoid this would be to create a new object type in GTK that derives + from GMenu say GtkMenuObject (GtkMenu is already taken ;) and make it implement + GtkBuildable iface. + + * GAction, GSimpleAction, GActionGroup + +GTK+ 3.4 + + * GtkApplication (add buildable iface to support GMenuModel?¿) + +GTK+ 3.14 + + * type="action" children in GtkDialog + +GTK+ 3.20 + + * GtkShortcutsWindow diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..b43bfed --- /dev/null +++ b/data/.gitignore @@ -0,0 +1,4 @@ +/org.gnome.Glade.desktop +/org.gnome.Glade.desktop.in +/gladeui-1.0.pc +/org.gnome.Glade.appdata.xml diff --git a/data/gettext/its/glade-catalog.its b/data/gettext/its/glade-catalog.its new file mode 100644 index 0000000..5597788 --- /dev/null +++ b/data/gettext/its/glade-catalog.its @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + diff --git a/data/gettext/its/glade-catalog.loc b/data/gettext/its/glade-catalog.loc new file mode 100644 index 0000000..bebd91f --- /dev/null +++ b/data/gettext/its/glade-catalog.loc @@ -0,0 +1,6 @@ + + + + + + diff --git a/data/icons/deprecated-16x16.png b/data/icons/deprecated-16x16.png new file mode 100644 index 0000000000000000000000000000000000000000..d5cc47ad69756971e452c02c40890101327f9b82 GIT binary patch literal 437 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|*pj^6U4S$Y z{B+)352QE?JR*x37`TN&n2}-D90{Nxdx@v7EBkF`PBBig%@&K!0EHw=Tq8=H^K)}k z^GX<;i&7IyQd1PlGfOfQ+&z5*!W;R-fmR84x;TbdoL+lb-;X&^fbB!PdCUV(m8Q4` zlcQUi81r;JU0qq{aJaa#dcIW?RS<}Fzg*C$q&4w9*Nn(bksM8Blho6m{l53-{Y#Ij zmjr@)3dNq;X6c-oW1d^H-ns6;-2G2Cp85IjID4qq`5k9w{w_W9^V#oa zzbE}h&;R8wTs@`CB0v37_pJL;MGV(c&pqbbd2B^%<7uU!W?z@Mf0Vp*TmCcEy@TLbR-Va4aU*u4b3FZFm$ zy`=f<#beVr`OhCRr|#8{dTDYt<`L6vqvxJ8KeOE4plf~O*8@YjeID+SPZ!Hvu0Q{i Z+v{}AwNDO3?!W+K@O1TaS?83{1OU7QsdWGV literal 0 HcmV?d00001 diff --git a/data/icons/deprecated-22x22.png b/data/icons/deprecated-22x22.png new file mode 100644 index 0000000000000000000000000000000000000000..26456e6672b51dbc7504038e676f62aa6881933f GIT binary patch literal 671 zcmV;Q0$}}#P)Px#24YJ`L;wH)0002_L%V+f000SaNLh0L01FcU01FcV0GgZ_00007bV*G`2ipS) z76mUrh-JtC000?uMObu0Z*6U5Zgc=ca%Ew3Wn>_CX>@2HM@dakSAh-}0005{NklynoxH!>Sk~Pyhp<4{T`Z23P`L zfJJU*ZT`Iu=n+r?8LO=ZCb`*oCAf<53Ge}UD9M0@PPdfS*DVSlO8W)8%FVw01s3!( z@Ica7r>Q$`mFZSTl70iv6Fj)RQ$cIsCvevV1<=}^_SW*9ruE=rx-IQQ4*OM%2P=|$ z0PJh;iR5ju2+Dq$uAiH|0Z#SZ3U^R|Q~9I>mQ{=~C<&PoHPJO^4nC}6+;h-9VCbMT zy-&o*fFdZvs3vO{!x?Z6tOJKtjGI-Ao4}#8a|cZ^U+8lXR6eMrnh6>Km%tYAQO_1| zskNphMnAZYT6Sdr1#kg8)pH@O%@SiHa8DN0`?!!aE8tT)X*#*IHcO0~;Eq3Nflh%P z;0k!D=L*=-TGJ9^8C<{%wW+VwUEmft&dsiKvuog3+ODs0`4%aOK_yaapp-2QFwf1- zTr$sqc}oM8V${H*=UV!$HoOO(wKTx0?)wV7*898MZ0x7ocg>4hV{qODZNP^bvl;Ls zH+$TiZX=Dp40r^r`IvW%%{A@KYgRACgyfA=vn&ge|$BSTQck~YyI6f>lK5fVfcw5x>( zE)thP25lAFS{b+y%cMZig+q%lB2cp!lp=MM%pNMv>)x5U_q+FNF%Lw7T6o}W4(I&N zi$5H)`5I5183N?L+#jsy22|w$;6^AE8tZ@ZlGzWRF%6SMDoG}jMoQ3B1x?i`)3QvC z3<7BC0l?VsM3}jyD2b#&JeeY%NRcwq7-pJe%EYHbY#8#WU(v@%d&7?C!eBFU0%lC_Lgg# zae1ew_r-p|?e_L|e(j}6q|9P>VcW&=R8_(6QHgJF5-cs{;o!hEz*_(e_4W<0_I*27 zoOh?#9crtNu(G^BTgzE>{P~y)TmUQ;xYE(lL1gtOhM7hPfe->m2&9xnfV2&CH_O-6 zH8&rc%`FD<>F6uD{yU}!TVUBXmSqu3q>ye7ARLFC%@rz3HJ*)*;PI8*v$L5806M@- zct%-XoO5bwYc;P&1HiIugqyp*uIn_^*W(BqV5v}C1o#61np<16%a<FFsVpFV<=*Z-!0N}vmP47>(@0J8CPvvu=Uml&U%u>lzfRowj_K^@P(s55x9 ldbl8-V|6FPil%eu_6M|H9_GvDD2D(5002ovPDHLkV1kkmCp`cF literal 0 HcmV?d00001 diff --git a/data/icons/fixed-bg.png b/data/icons/fixed-bg.png new file mode 100644 index 0000000000000000000000000000000000000000..1f8c446f0cba7dc89741673c1d9478c05f7abae1 GIT binary patch literal 216 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 EFHrgSm;e9( literal 0 HcmV?d00001 diff --git a/data/icons/hicolor/scalable/apps/org.gnome.Glade.svg b/data/icons/hicolor/scalable/apps/org.gnome.Glade.svg new file mode 100644 index 0000000..0542b67 --- /dev/null +++ b/data/icons/hicolor/scalable/apps/org.gnome.Glade.svg @@ -0,0 +1,1845 @@ + + + + + Adwaita Icon Template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + GNOME Design Team + + + + + Adwaita Icon Template + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/icons/hicolor/symbolic/apps/glade-brand-symbolic.svg b/data/icons/hicolor/symbolic/apps/glade-brand-symbolic.svg new file mode 100644 index 0000000..a3964b8 --- /dev/null +++ b/data/icons/hicolor/symbolic/apps/glade-brand-symbolic.svg @@ -0,0 +1,29 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/data/icons/hicolor/symbolic/apps/org.gnome.Glade-symbolic.svg b/data/icons/hicolor/symbolic/apps/org.gnome.Glade-symbolic.svg new file mode 100644 index 0000000..2a835fd --- /dev/null +++ b/data/icons/hicolor/symbolic/apps/org.gnome.Glade-symbolic.svg @@ -0,0 +1,125 @@ + + + + + + + + image/svg+xml + + Gnome Symbolic Icon Theme + + + + + + + Gnome Symbolic Icon Theme + + + + + + + + + + diff --git a/data/icons/placeholder.png b/data/icons/placeholder.png new file mode 100644 index 0000000000000000000000000000000000000000..8f4eb294d107bbf306b12a4178dd8d590fea18bd GIT binary patch literal 240 zcmeAS@N?(olHy`uVBq!ia0vp^93U(JBp5b+u@wPQoCO|{#X#BvjNMLV+kphj3LMjc zG*~r*VZ_!PAV=HN#W95Adh(6Ezi(~5yF2m8ySqFFY(5={=0ZEzSY|LqY*`@570eaf zuE@Z!w?5$^vxm`Apf=SK*NBpo#FA921~V%YGl+&$HYIm~8W=oX{an^LB{Ts5N!LEH literal 0 HcmV?d00001 diff --git a/data/icons/plus.png b/data/icons/plus.png new file mode 100644 index 0000000000000000000000000000000000000000..313f44536446a4aa5fff5a04394114c2149e90a3 GIT binary patch literal 147 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmUKs7M+U~W1%@xC#RK_Xo-U3d z6}OH~aO7f8;9$A?;eY%+Jx;Z(D@$!PEsVH06+Q1WH@t6GVN^(5$G$6uS!%Ao`AW<1 t?JPeWPoBv6e6XzenbyyTYg@xOeHwX{_%)N)6#-3U@O1TaS?83{1OQ6dE)@U( literal 0 HcmV?d00001 diff --git a/data/meson.build b/data/meson.build new file mode 100644 index 0000000..9f24456 --- /dev/null +++ b/data/meson.build @@ -0,0 +1,67 @@ +desktop_conf = configuration_data() +desktop_conf.set('VERSION', glade_version) + +i18n.merge_file( + 'desktop', + type: 'desktop', + input: configure_file( + input: 'org.gnome.Glade.desktop.in.in', + output: '@BASENAME@', + configuration: desktop_conf, + ), + output: '@BASENAME@', + po_dir: po_dir, + install: true, + install_dir: glade_datadir / 'applications', +) + +appdata = glade_name + '.appdata.xml' + +i18n.merge_file( + 'appdata', + input: 'org.gnome.Glade.appdata.xml.in', + output: '@BASENAME@', + po_dir: po_dir, + install: true, + install_dir: glade_datadir / 'metainfo', +) + +# FIXME: replace these by install_subdir once autotools is removed +install_data( + 'icons/hicolor/scalable/apps/org.gnome.Glade.svg', + install_dir: glade_datadir / 'icons/hicolor/scalable/apps', +) + +icon_symbolic_data = files( + 'icons/hicolor/symbolic/apps/glade-brand-symbolic.svg', + 'icons/hicolor/symbolic/apps/org.gnome.Glade-symbolic.svg', +) + +install_data( + icon_symbolic_data, + install_dir: glade_datadir / 'icons/hicolor/symbolic/apps', +) + +pixmaps_data = files( + 'icons/deprecated-16x16.png', + 'icons/deprecated-22x22.png', + 'icons/devhelp.png', + 'icons/fixed-bg.png', + 'icons/placeholder.png', + 'icons/plus.png', +) + +install_data( + pixmaps_data, + install_dir: glade_datadir / glade_pixmapdir, +) + +its_data = files( + 'gettext/its/glade-catalog.its', + 'gettext/its/glade-catalog.loc', +) + +install_data( + its_data, + install_dir: glade_datadir / 'gettext/its', +) diff --git a/data/org.gnome.Glade.appdata.xml.in b/data/org.gnome.Glade.appdata.xml.in new file mode 100644 index 0000000..196b0b8 --- /dev/null +++ b/data/org.gnome.Glade.appdata.xml.in @@ -0,0 +1,164 @@ + + + org.gnome.Glade + CC0-1.0 + GPL-2.0+ and LGPL-2.0+ + Glade + Create or open user interface designs for GTK+ applications + + + +

+ Glade is a RAD tool to enable quick & easy development of user interfaces + for the GTK+ 3 toolkit and the GNOME desktop environment. +

+ + +

+ The user interfaces designed in Glade are saved as XML and these can be + loaded by applications dynamically as needed by using GtkBuilder or used + directly to define a new GtkWidget derived object class using + GTK+ new template feature. +

+ + +

+ By using GtkBuilder, Glade XML files can be used in numerous programming + languages including C, C++, C#, Vala, Java, Perl, Python, and others. +

+
+ + + https://glade.gnome.org/images/glade-main-page.png + + + + glade.desktop + + org.gnome.Glade.desktop + https://glade.gnome.org/ + https://gitlab.gnome.org/GNOME/glade/issues + https://www.gnome.org/friends/ + https://help.gnome.org/users/glade/stable/ + juanpablougarte_at_gmail.com + + HiDpiIcon + ModernToolkit + UserDocs + + GNOME + glade + + + +

Glade 3.38.2 release!

+
    +
  • Fixed windows compatibility issues.
  • +
  • Fix build against Python 3.9 (Jan Alexander Steffens)
  • +
+
+ + Issue #355 "Make notification text selectable" + Issue #53 "Can't choose a FileChooserDialog for a FileChooserButton" + Issue #371 "Glade user survey not working (TLS error)" + +
+ + +

Glade 3.38.1 release!

+
    +
  • Fixed various stability and functionality issues.
  • +
  • Updated translations.
  • +
+
+ + Issue #471: XML not updated after adding element + Issue #480: version field not always updated in XML + Issue #474: GtkLabel: setup text attribute issues + Issue #479: Glade 3.36.0 segfaults when opening a file + +
+ + +

Glade 3.38.0 release!

+
    +
  • Load template files as new GTypes and add them to "User templates" widget group automatically without the need of a catalog
  • +
  • Added JavaScript widget support
  • +
  • Use version data from gir to improve deprecation/target tests
  • +
  • Implement survey using new backend at surveys.gnome.org
  • +
  • Keep survey state in config file
  • +
  • Associate with application/x-gtk-builder (Caolán McNamara)
  • +
  • Project properties dialog improvements. (Use headerbar, stack and stach switcher, add warnings textview)
  • +
  • Improve toolkit target version selection in project properties dialog
  • +
  • Improve treemodel char data type handling
  • +
  • Remove autotools (Iñigo Martínez)
  • +
  • New gjs-1.0 1.64 dependency
  • +
  • Bump Gtk dependency to 3.24
  • +
  • Bump webkit2gtk dependency to 2.28
  • +
  • Fixed all compiler/gir warnings
  • +
+
+ + Issue #385: GtkCellRendererText causes deprecated warnings for ghost properties + Issue #444: show-emoji-icon is missing "minimum version"/since constraint + Issue #367: Glade removes double/triple slashes from URLs + Issue #452: GtkComboBox cell renderers editor is too hard to discover + Issue #363: Toplevel windows are leaked when closing a project + Issue #462: Crash when creating a new GtkAssistant and pressing it + Issue #463: Specify file on command line + Issue #461: Glade resets number of rows and columns in GtkGrid + Issue #460: 3.37.0: test suite fails because glade uses incorrectly asserts in test units + Issue #412: Fix inconsistent "top-level", "top level", "toplevel" spelling in translatable strings + Issue #281: GladeProperty: Do not replace - with _ + Issue #389: GladeProject: show message on invalid GtkBuilder file + Issue #446: GladeGtkListBox: fix size request issue + Issue #432: GladeStandarStock, GladeStandarStockImage: fix get_type() functions for introspection + Issue #447: GladeGtkWindow: Fix CSD support + Issue #456: Utils: use g_param_spec_uchar() for uchar types + Issue #459: Gtk catalog: add GtkRecentChooserMenu to Display group + Issue #433: Implement survey using new backend at surveys.gnome.org + Build: use gtk-mac-integration only when creating bundle (Tom Schoonjans) + Ignore locale when saving numeric values (Alberto Fanjul) + Remove config for external builds (Alberto Fanjul) + Python plugin: fix linking error + Tests: add python and javascript test case + gtk: Add GtkScrolledWindow propagate-natural-width property (Corentin Noël) + GladeScrolledWindowEditor: Squeeze width/height properties in two columns + Gtk plugin: add missing displayable values + Webkit2gtk plugin: add missing displayable values and sandbox web view + dtd: Make init-function an element, not an attribute (Adrien Plazas) + +
+ + +
+ + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + none + +
diff --git a/data/org.gnome.Glade.desktop.in.in b/data/org.gnome.Glade.desktop.in.in new file mode 100644 index 0000000..f3ff805 --- /dev/null +++ b/data/org.gnome.Glade.desktop.in.in @@ -0,0 +1,19 @@ +[Desktop Entry] +Name=Glade +GenericName=Interface Designer +Comment=Create or open user interface designs for GTK+ applications +# Translators: Search terms to find this application. Do NOT translate or localize the semicolons! The list MUST also end with a semicolon! +Keywords=GUI designer;user interface;ui builder; +Exec=glade %F +Terminal=false +StartupNotify=true +Type=Application +# Translators: Do NOT translate or transliterate this text (this is an icon file name)! +Icon=org.gnome.Glade +Categories=GNOME;GTK;Development;GUIDesigner; +MimeType=application/x-gtk-builder;application/x-glade; +X-GNOME-DocPath=glade/glade.xml +X-GNOME-Bugzilla-Bugzilla=GNOME +X-GNOME-Bugzilla-Product=glade +X-GNOME-Bugzilla-Version=@VERSION@ +X-GNOME-Bugzilla-Component=general diff --git a/doc/.gitignore b/doc/.gitignore new file mode 100644 index 0000000..f7551a3 --- /dev/null +++ b/doc/.gitignore @@ -0,0 +1 @@ +/version.xml diff --git a/doc/catalogintro.sgml b/doc/catalogintro.sgml new file mode 100644 index 0000000..8abfce2 --- /dev/null +++ b/doc/catalogintro.sgml @@ -0,0 +1,212 @@ + + + Introducing the Glade Catalog + Glade UI + + + Writing catalogs + +How to write and install a catalog + + + + + Introduction + + +You can provide support for your custom widgets in a few ways, you can +make a package and install it to the system directories, load additional +catalogs in user directories, project directories for example, and +you can optionally provide code support and/or icons, normally you need +to at least have the object type in a library somewhere, but you can work +around this using the 'parent' property described in the next section. +If you dont provide icons for the inspector and palette Glade will simply +print a warning and use a default icon. The catalog file is written in an +XML format and a DTD for the format can be found in the plugins/ directory +of the Glade tarball. + + + +In most cases gtk+ derived widgets can be added with little effort and it +is enough to simply specify the widget's type; glade will introspect +its properties and signals - but due to the organic nature of a widget +toolkit there are always exceptions. In this document we'll try to provide +some basic examples and describe a wealth of options that can be used to +enhance UI editing and workaround exceptions. + + + +The catalog file starts by specifying the name of the catalog and the plugin +library to use, the following examples assume you have a namespace "Foo" and +are integrating an object "Frobnicator": + + + + my_catalog_init + + + + + ... widget classes go here + + + + + ... widget class references go here + + + ... widget groups go here +]]> + + + + + Toplevel catalog properties and tags + +When defining the catalog, the 'name' and 'library' +are both mandatory attributes of the 'glade-catalog' tag; optionally +you can also use 'icon-prefix', 'depends' and 'domain'. + + + + + name + + +A string identifier for the catalog in question, it will be used to identify your +catalog so that the glade file can explicitly require it and to manage inter +catalog dependencies. + + + + + + version + + +A 'major.minor' formed version describing the current version of underlying widget kit; +example: version="1.0". This is needed for version checking to work. +Please note that all versioning related support is completely optional. + + + + + + targetable + + +A comma separated list of 'major.minor' formed versions describing sensible previous +targetable versions of the underlying toolkit not including the current version; +example: targetable="0.6,0.8". + + + + + + icon-prefix + + +Used to form icon names for widgets. This property defaults to the value of the 'name' attribute. + + + + + + library + + +Used to load the types and introspect properties, unless you are faking your widget +classes (which will be described later on), glade will need to load this library, +it can either be the name of the library containing the widgets or the plugin library +which is assumed to implicitly link to your widget library. The library will be loaded +either by a user specified path, the system plugin directory: +$prefix/lib/glade-3/modules/, or from the default system library +paths in the afore mentioned order of precedence. + + + + + + depends + + +Used for inheritance of support code to work properly (i.e. if your object derives +from an object in gtk+, you'll want the default support code in the gladegtk plugin +to be enabled for your widget too). This property's value is the `name' property of +another installed glade plugin; usually you'll want to declare: 'depends="gtk+"' +for your plugin. + + + + + + domain + + +The domain in which to search for translatable strings from the +catalog file; please note that all strings from the catalog that will appear in the UI are +translated using this domain. If the 'domain' is not specified, the library property will +be used in it's stead. + + + + + + book + + +Used to specify a namespace to search devhelp docs library with +(specifically, it is the $(DOC_MODULE) that you specified in your gtk-doc Makefile.am). + + + + + + init-function + + +Used to retrieve an optional global entry point to your plugin; +if you need to initialize any backends or whatnot this is a good place. +Your catalog's init-function will be called before any widget classes are instantiated. + + + + + + + Validating and installing + +The DTD that is shipped with Glade can be used to validate your catalog +file. Note that properties must be entered in the same order as they are +specified in the DTD for the validation to pass. + + +To validate a file, do this: + xmllint --dtdvalid glade-catalog.dtd --noout my-catalog.xml + + +To install a widget plugin, the catalog XML file should be copied into +the catalog directory, which can be retrieved as: + pkg-config --variable=catalogdir gladeui-1.0 +The plugin library should be installed into the modules directory: + pkg-config --variable=moduledir gladeui-1.0 +Widget icons if provided (recommended) need to be installed into the icon theme, +this is described in the next chapter. + + +You can also load your catalog from a user directory by specifying +additional load path(s) in the environment, for instance: + GLADE_CATALOG_SEARCH_PATH=~/mycatalogs:~/work/foo/glade + + + +Same goes for optional plugin libraries, for instance: + GLADE_MODULE_SEARCH_PATH=~/work/foo/src + + + +Currently loading icons without installing them is unsupported. + + + + diff --git a/doc/gladegjs.sgml b/doc/gladegjs.sgml new file mode 100644 index 0000000..468589d --- /dev/null +++ b/doc/gladegjs.sgml @@ -0,0 +1,99 @@ + + + JavaScript Gtk widgets support + Glade UI + + + Add GJS/JavaScript support to your catalog + +How to write and install a catalog for a JavaScript widget library + + + + + Introduction + +Glade supports loading widgets programed in JavaScript by linking and running GJS from the gladegjs catalog plugin. + + + +So in order for glade to support your JavaScript widgets you will have to: + + +a) specify gladegjs support code as your plugin library. + + + +b) set glade_gjs_init as you init function. + + + +c) make sure your catalog name is the same as your JavaScript import library since +glade_gjs_init() will use this name to import your widgets into the +interpreter. + + + + + glade_gjs_init + + + + + + + + +]]> + + + + +GJS will look up for your widgets in the same places it looks +for regular catalogs plugins, that is $GLADE_ENV_MODULE_PATH +environment variable and `pkg-config --variable=moduledir gladeui-2.0` + +So the easiest thing would be to make a symlink in one of those directory, just +do not forget that the name should be the one specified in your catalog name. + + + +gjsplugin.js + + + + + + diff --git a/doc/gladepython.sgml b/doc/gladepython.sgml new file mode 100644 index 0000000..d8b00a1 --- /dev/null +++ b/doc/gladepython.sgml @@ -0,0 +1,92 @@ + + + Python Gtk widgets support + Glade UI + + + Add python support to your catalog + +How to write and install a catalog for a python widget library + + + + + Introduction + +Glade supports loading widgets coded in python by linking and running the python +interpreter from the gladepython catalog plugin. + + + +So in order for glade to include your python gtk widgets you will have to: + + +a) specify gladepython support code as your plugin library. + + + +b) set glade_python_init as you init function. + + + +c) make sure your catalog name is the same as your python import library since +glade_python_init() will use this name to import your widgets into the +interpreter. + + +pythonplugin.xml + + + glade_python_init + + + + + + + + +]]> + + + + +Glade's python interpreter will look up for your widgets in the same +places it looks +for regular catalogs plugins, that is $GLADE_ENV_MODULE_PATH +environment variable +and `pkg-config --variable=moduledir gladeui-2.0` + +So the easiest thing would be to make a symlink in one of those directory, just +do not forget that the name should be the one specified in your catalog name. + + + +pythonplugin.py + + + + + + diff --git a/doc/gladeui-docs.xml b/doc/gladeui-docs.xml new file mode 100644 index 0000000..7e867ff --- /dev/null +++ b/doc/gladeui-docs.xml @@ -0,0 +1,90 @@ + + + +]> + + + Glade User Interface Designer Reference Manual + + for gladeui &version;. + This document attempts to describe how to use the Glade UI Designer core + library to integrate Glade into your application and how to integrate your + custom GTK+ derived widgets into the Glade UI Designer. + + The latest version of this documentation can be found on-line at + + https://developer.gnome.org/gladeui/stable + . + + + + + Adding your custom widgets + + + + + + + + + Glade Core + + + + + + + + + + + + + + + + + + + + + + + Dockable Glade UI Widgets + + + + + + + + + + + + + + + + Miscellaneous utilities + + + + + + + + Index + + + + Index of deprecated symbols + + + + + diff --git a/doc/gladeui-sections.txt b/doc/gladeui-sections.txt new file mode 100644 index 0000000..a06e801 --- /dev/null +++ b/doc/gladeui-sections.txt @@ -0,0 +1,1545 @@ +
+glade-widget +GladeWidget +GladeWidget +glade_widget_get_from_gobject +glade_widget_add_child +glade_widget_remove_child +glade_widget_replace +glade_widget_rebuild +glade_widget_read +glade_widget_write +glade_widget_read_child +glade_widget_write_child +glade_widget_write_placeholder +glade_widget_write_special_child_prop +glade_widget_create_editor_property +glade_widget_set_child_type_from_node +glade_widget_dup +glade_widget_copy_signals +glade_widget_copy_properties +glade_widget_set_packing_properties +glade_widget_get_property +glade_widget_get_pack_property +glade_widget_dup_properties +glade_widget_remove_property +glade_widget_show +glade_widget_hide +glade_widget_add_signal_handler +glade_widget_remove_signal_handler +glade_widget_change_signal_handler +glade_widget_list_signal_handlers +glade_widget_has_decendant +glade_widget_event +glade_widget_placeholder_relation +glade_widget_get_action +glade_widget_get_pack_action +glade_widget_set_action_visible +glade_widget_set_action_sensitive +glade_widget_set_pack_action_sensitive +glade_widget_add_prop_ref +glade_widget_remove_prop_ref +glade_widget_object_set_property +glade_widget_object_get_property +glade_widget_child_set_property +glade_widget_child_get_property +glade_widget_property_get +glade_widget_property_set +glade_widget_pack_property_get +glade_widget_pack_property_set +glade_widget_property_reset +glade_widget_pack_property_reset +glade_widget_property_default +glade_widget_property_original_default +glade_widget_pack_property_default +glade_widget_property_set_sensitive +glade_widget_pack_property_set_sensitive +glade_widget_property_set_enabled +glade_widget_pack_property_set_enabled +glade_widget_property_set_save_always +glade_widget_pack_property_set_save_always +glade_widget_property_string +glade_widget_pack_property_string +glade_widget_set_name +glade_widget_get_name +glade_widget_set_internal +glade_widget_get_internal +glade_widget_get_object +glade_widget_set_project +glade_widget_get_project +glade_widget_get_adaptor +glade_widget_get_parent +glade_widget_set_parent +glade_widget_superuser +glade_widget_push_superuser +glade_widget_pop_superuser +glade_widget_add_verify +glade_widget_depends +glade_widget_ensure_name +glade_widget_find_child +glade_widget_generate_path_name +glade_widget_get_actions +glade_widget_get_children +glade_widget_get_device_from_event +glade_widget_get_display_name +glade_widget_set_is_composite +glade_widget_get_is_composite +glade_widget_get_locker +glade_widget_get_pack_actions +glade_widget_get_packing_properties +glade_widget_get_parentless_reffed_widgets +glade_widget_get_parentless_widget_ref +glade_widget_get_properties +glade_widget_get_signal_list +glade_widget_get_signal_model +glade_widget_get_toplevel +glade_widget_has_name +glade_widget_has_prop_refs +glade_widget_set_in_project +glade_widget_in_project +glade_widget_is_ancestor +glade_widget_list_locked_widgets +glade_widget_list_prop_refs +glade_widget_lock +glade_widget_unlock +glade_widget_set_pack_action_visible +glade_widget_set_support_warning +glade_widget_support_changed +glade_widget_support_warning +glade_widget_verify +glade_widget_write_signals +IS_GLADE_WIDGET_EVENT +GLADE_UNNAMED_PREFIX + +GLADE_WIDGET +GLADE_IS_WIDGET +GLADE_TYPE_WIDGET +glade_widget_get_type +GLADE_WIDGET_CLASS +GLADE_IS_WIDGET_CLASS +GLADE_WIDGET_GET_CLASS +GladeWidgetClass +GladeWidgetPrivate +
+ +
+glade-init +Initializing the library +glade_init + +GLADE_TYPE_DEBUG_FLAG +glade_debug_flag_get_type +GLADE_TYPE_CURSOR_TYPE +glade_cursor_type_get_type +GLADE_MAKE_EPROP +GLADE_TYPE_EPROP_CHECK +glade_eprop_check_get_type +_GladeDrag +_GladeDragInterface +GLADE_DRAG +GLADE_DRAG_GET_INTERFACE +GLADE_IS_DRAG +GLADE_TYPE_DRAG +GLADE_DND_INFO_DATA +GLADE_DND_TARGET_DATA +
+ +
+glade-object-stub +GladeObjectStub +GladeObjectStub + +GLADE_IS_OBJECT_STUB +GLADE_IS_OBJECT_STUB_CLASS +GLADE_OBJECT_STUB +GLADE_OBJECT_STUB_CLASS +GLADE_OBJECT_STUB_GET_CLASS +GLADE_TYPE_OBJECT_STUB +glade_object_stub_get_type +GladeObjectStubClass +GladeObjectStubPrivate +
+ +
+glade-signal-model +GladeSignalModel +GladeSignalModel +GladeSignalModelColumns +glade_signal_model_new + +GladeSignalModelClass +GladeSignalModelPrivate +GLADE_TYPE_SIGNAL_MODEL +GLADE_TYPE_SIGNAL_MODEL_COLUMNS +GLADE_IS_SIGNAL_MODEL +GLADE_IS_SIGNAL_MODEL_CLASS +GLADE_SIGNAL_MODEL +GLADE_SIGNAL_MODEL_CLASS +GLADE_SIGNAL_MODEL_GET_CLASS +glade_signal_model_columns_get_type +glade_signal_model_get_type +
+ +
+glade-adaptor-chooser +GladeAdaptorChooser +GladeAdaptorChooser +GladeAdaptorChooserWidget +glade_adaptor_chooser_new +glade_adaptor_chooser_get_project +glade_adaptor_chooser_set_project + +GLADE_ADAPTOR_CHOOSER_WIDGET +GLADE_ADAPTOR_CHOOSER_WIDGET_CLASS +GLADE_ADAPTOR_CHOOSER_WIDGET_GET_CLASS +GLADE_IS_ADAPTOR_CHOOSER_WIDGET +GLADE_IS_ADAPTOR_CHOOSER_WIDGET_CLASS +GLADE_TYPE_ADAPTOR_CHOOSER +GLADE_TYPE_ADAPTOR_CHOOSER_WIDGET +GladeAdaptorChooserWidgetClass +_GladeAdaptorChooserWidget +_GladeAdaptorChooserWidgetClass +_GladeAdaptorChooserWidgetFlags +_GladeAdaptorChooserWidgetPrivate +
+ +
+glade-widget-adaptor +GladeWidgetAdaptor +GladeCreateReason +GWA_IS_TOPLEVEL +GWA_USE_PLACEHOLDERS +GWA_DEFAULT_WIDTH +GWA_DEFAULT_HEIGHT +GWA_GET_CLASS +GWA_GET_OCLASS +GWA_SCROLLABLE_WIDGET +GLADE_VALID_CREATE_REASON +GLADE_TYPE_CREATE_REASON +GLADE_WIDGET_ADAPTOR_INSTANTIABLE_PREFIX +GladeWidgetAdaptor +GladeActionActivateFunc +GladeActionSubmenuFunc +GladeAddChildFunc +GladeAddChildVerifyFunc +GladeChildActionActivateFunc +GladeChildGetPropertyFunc +GladeChildSetPropertyFunc +GladeChildVerifyPropertyFunc +GladeConstructObjectFunc +GladeCreateEditableFunc +GladeCreateEPropFunc +GladeCreateWidgetFunc +GladeDependsFunc +GladeDestroyObjectFunc +GladeGetChildrenFunc +GladeGetInternalFunc +GladeGetPropertyFunc +GladePostCreateFunc +GladeReadWidgetFunc +GladeRemoveChildFunc +GladeReplaceChildFunc +GladeSetPropertyFunc +GladeStringFromValueFunc +GladeVerifyPropertyFunc +GladeWriteWidgetFunc +glade_widget_adaptor_create_widget +glade_widget_adaptor_create_eprop +glade_widget_adaptor_from_pspec +glade_widget_adaptor_from_catalog +glade_widget_adaptor_register +glade_widget_adaptor_create_internal +glade_widget_adaptor_create_widget_real +glade_widget_adaptor_get_by_name +glade_widget_adaptor_get_by_type +glade_widget_adaptor_get_property_def +glade_widget_adaptor_get_pack_property_def +glade_widget_adaptor_default_params +glade_widget_adaptor_post_create +glade_widget_adaptor_get_internal_child +glade_widget_adaptor_set_property +glade_widget_adaptor_get_property +glade_widget_adaptor_verify_property +glade_widget_adaptor_add +glade_widget_adaptor_remove +glade_widget_adaptor_get_children +glade_widget_adaptor_has_child +glade_widget_adaptor_child_set_property +glade_widget_adaptor_child_get_property +glade_widget_adaptor_child_verify_property +glade_widget_adaptor_replace_child +glade_widget_adaptor_read_child +glade_widget_adaptor_read_widget +glade_widget_adaptor_write_child +glade_widget_adaptor_write_widget +glade_widget_adaptor_query +glade_widget_adaptor_get_packing_default +glade_widget_adaptor_is_container +glade_widget_adaptor_action_add +glade_widget_adaptor_pack_action_add +glade_widget_adaptor_action_remove +glade_widget_adaptor_pack_action_remove +glade_widget_adaptor_pack_actions_new +glade_widget_adaptor_action_activate +glade_widget_adaptor_child_action_activate +glade_widget_adaptor_string_from_value +glade_widget_adaptor_action_submenu +glade_widget_adaptor_actions_new +glade_widget_adaptor_add_verify +glade_widget_adaptor_construct_object +glade_widget_adaptor_create_editable +glade_widget_adaptor_create_eprop_by_name +glade_widget_adaptor_depends +glade_widget_adaptor_destroy_object +glade_widget_adaptor_get_book +glade_widget_adaptor_get_catalog +glade_widget_adaptor_get_display_name +glade_widget_adaptor_get_generic_name +glade_widget_adaptor_get_icon_name +glade_widget_adaptor_get_missing_icon +glade_widget_adaptor_get_name +glade_widget_adaptor_get_object_type +glade_widget_adaptor_get_packing_props +glade_widget_adaptor_get_parent_adaptor +glade_widget_adaptor_get_properties +glade_widget_adaptor_get_signal_def +glade_widget_adaptor_get_signals +glade_widget_adaptor_get_title +glade_widget_adaptor_has_internal_children +glade_widget_adaptor_list_adaptors +glade_widget_adaptor_write_widget_after + +GLADE_WIDGET_ADAPTOR +GLADE_IS_WIDGET_ADAPTOR +GLADE_TYPE_WIDGET_ADAPTOR +glade_widget_adaptor_get_type +glade_create_reason_get_type +GLADE_WIDGET_ADAPTOR_CLASS +GLADE_IS_WIDGET_ADAPTOR_CLASS +GLADE_WIDGET_ADAPTOR_GET_CLASS +GladeWidgetAdaptorClass +GladeWidgetAdaptorPrivate +
+ +
+glade-catalog +GladeCatalog +GladeCatalog +GladeCatalogInitFunc +GladeTargetableVersion +GladeWidgetGroup +glade_catalog_load_all +glade_catalog_destroy_all +glade_catalog_add_path +glade_catalog_remove_path +glade_catalog_get_adaptors +glade_catalog_is_loaded +glade_catalog_get_book +glade_catalog_get_domain +glade_catalog_get_extra_paths +glade_catalog_get_icon_prefix +glade_catalog_get_major_version +glade_catalog_get_minor_version +glade_catalog_get_name +glade_catalog_get_prefix +glade_catalog_get_targets +glade_catalog_get_widget_groups +glade_widget_group_get_adaptors +glade_widget_group_get_expanded +glade_widget_group_get_name +glade_widget_group_get_title + +GLADE_CATALOG +GLADE_IS_CATALOG +GLADE_IS_WIDGET_GROUP +GLADE_WIDGET_GROUP +
+ +
+glade-design-view +GladeDesignView +GladeDesignView +glade_design_view_new +glade_design_view_get_project +glade_design_view_get_from_project + +GLADE_DESIGN_VIEW +GLADE_IS_DESIGN_VIEW +GLADE_TYPE_DESIGN_VIEW +glade_design_view_get_type +GLADE_DESIGN_VIEW_CLASS +GLADE_IS_DESIGN_VIEW_CLASS +GLADE_DESIGN_VIEW_GET_CLASS +GladeDesignViewClass +GladeDesignViewPrivate +
+ +
+glade-property-def +GladePropertyDef +GladePropertyDef +glade_property_def_new +glade_property_def_new_from_spec +glade_property_def_clone +glade_property_def_free +glade_property_def_is_visible +glade_property_def_is_object +glade_property_def_make_flags_from_string +glade_property_def_make_gvalue_from_string +glade_property_def_make_string_from_gvalue +glade_property_def_make_gvalue_from_vl +glade_property_def_set_vl_from_gvalue +glade_property_def_make_gvalue +glade_property_def_get_from_gvalue +glade_property_def_update_from_node +glade_property_def_make_adjustment +glade_property_def_match +glade_property_def_void_value +glade_property_def_atk +glade_property_def_common +glade_property_def_compare +glade_property_def_create_type +glade_property_def_custom_layout +glade_property_def_deprecated +glade_property_def_get_adaptor +glade_property_def_get_construct_only +glade_property_def_get_default +glade_property_def_get_default_from_spec +glade_property_def_get_ignore +glade_property_def_get_is_packing +glade_property_def_get_name +glade_property_def_get_original_default +glade_property_def_get_pspec +glade_property_def_get_tooltip +glade_property_def_get_virtual +glade_property_def_id +glade_property_def_load_defaults_from_spec +glade_property_def_multiline +glade_property_def_needs_sync +glade_property_def_new_from_spec_full +glade_property_def_optional +glade_property_def_optional_default +glade_property_def_parentless_widget +glade_property_def_query +glade_property_def_save +glade_property_def_save_always +glade_property_def_set_adaptor +glade_property_def_set_construct_only +glade_property_def_set_ignore +glade_property_def_set_is_packing +glade_property_def_set_name +glade_property_def_set_pspec +glade_property_def_set_tooltip +glade_property_def_set_virtual +glade_property_def_set_weights +glade_property_def_since_major +glade_property_def_since_minor +glade_property_def_stock +glade_property_def_stock_icon +glade_property_def_themed_icon +glade_property_def_transfer_on_paste +glade_property_def_translatable +glade_property_def_weight +GLADE_PROPERTY_DEF_OBJECT_DELIMITER + +GLADE_TYPE_PROPERTY_DEF +glade_property_def_get_type +GLADE_PROPERTY_DEF +GLADE_IS_PROPERTY_DEF +GLADE_PROPERTY_DEF_IS_TYPE +
+ +
+glade-name-context +GladeNameContext +GladeNameContext +glade_name_context_add_name +glade_name_context_destroy +glade_name_context_has_name +glade_name_context_n_names +glade_name_context_new +glade_name_context_new_name +glade_name_context_release_name + +
+ +
+glade-clipboard +GladeClipboard +GladeClipboard +glade_clipboard_new +glade_clipboard_add +glade_clipboard_clear +glade_clipboard_widgets +glade_clipboard_get_has_selection + +GLADE_CLIPBOARD +GLADE_IS_CLIPBOARD +GLADE_TYPE_CLIPBOARD +glade_clipboard_get_type +GladeClipboardClass +GladeClipboardPrivate +
+ +
+glade-signal +GladeSignal +GladeSignal +glade_signal_new +glade_signal_clone +glade_signal_equal +glade_signal_read +glade_signal_write +glade_signal_editor_enable_dnd +glade_signal_get_after +glade_signal_get_def +glade_signal_get_detail +glade_signal_get_handler +glade_signal_get_name +glade_signal_get_support_warning +glade_signal_get_swapped +glade_signal_get_userdata +glade_signal_set_after +glade_signal_set_detail +glade_signal_set_handler +glade_signal_set_support_warning +glade_signal_set_swapped +glade_signal_set_userdata + +GLADE_SIGNAL +GLADE_IS_SIGNAL +GLADE_IS_SIGNAL_CLASS +GLADE_TYPE_SIGNAL +GladeSignalClass +GladeSignalPrivate +GLADE_SIGNAL_GET_CLASS +GLADE_SIGNAL_CLASS +glade_signal_get_type +
+ +
+glade-signal-def +GladeSignalDef +GladeSignalDef +glade_signal_def_new +glade_signal_def_clone +glade_signal_def_free +glade_signal_def_update_from_node +glade_signal_def_get_adaptor +glade_signal_def_get_flags +glade_signal_def_get_name +glade_signal_def_get_object_type_name +glade_signal_def_set_deprecated +glade_signal_def_deprecated +glade_signal_def_set_since +glade_signal_def_since_major +glade_signal_def_since_minor + +GLADE_SIGNAL_DEF +GLADE_TYPE_SIGNAL_DEF +glade_signal_def_get_type +
+ +
+glade-editor-property +GladeEditorProperty +GladeEditorProperty +GLADE_MAKE_EPROP_TYPE +glade_editor_property_load +glade_editor_property_load_by_widget +glade_editor_property_commit +glade_editor_property_commit_no_callback +glade_editor_property_get_custom_text +glade_editor_property_get_disable_check +glade_editor_property_get_item_label +glade_editor_property_get_property_def +glade_editor_property_get_property +glade_editor_property_loading +glade_editor_property_set_custom_text +glade_editor_property_set_disable_check +glade_editor_property_show_i18n_dialog +glade_editor_property_show_object_dialog +glade_editor_property_show_resource_dialog + +GLADE_EDITOR_PROPERTY +GLADE_IS_EDITOR_PROPERTY +GLADE_TYPE_EDITOR_PROPERTY +glade_editor_property_get_type +GLADE_EDITOR_PROPERTY_CLASS +GLADE_IS_EDITOR_PROPERTY_CLASS +GLADE_EDITOR_PROPERTY_GET_CLASS +GLADE_TYPE_EPROP_ADJUSTMENT +GLADE_TYPE_EPROP_BOOL +GLADE_TYPE_EPROP_COLOR +GLADE_TYPE_EPROP_ENUM +GLADE_TYPE_EPROP_FLAGS +GLADE_TYPE_EPROP_NAMED_ICON +GLADE_TYPE_EPROP_NUMERIC +GLADE_TYPE_EPROP_OBJECT +GLADE_TYPE_EPROP_OBJECTS +GLADE_TYPE_EPROP_RESOURCE +GLADE_TYPE_EPROP_TEXT +GLADE_TYPE_EPROP_UNICHAR +glade_eprop_adjustment_get_type +glade_eprop_bool_get_type +glade_eprop_color_get_type +glade_eprop_enum_get_type +glade_eprop_flags_get_type +glade_eprop_named_icon_get_type +glade_eprop_numeric_get_type +glade_eprop_object_get_type +glade_eprop_objects_get_type +glade_eprop_resource_get_type +glade_eprop_text_get_type +glade_eprop_unichar_get_type +GladeEditorPropertyClass +
+ +
+glade-fixed +GladeFixed + +GLADE_FIXED +GLADE_IS_FIXED +GLADE_TYPE_FIXED +glade_fixed_get_type +GLADE_FIXED_CLASS +GLADE_IS_FIXED_CLASS +GLADE_FIXED_GET_CLASS +GladeFixedClass +GLADE_FIXED_CURSOR_TOP +GLADE_FIXED_CURSOR_BOTTOM +GLADE_FIXED_CURSOR_RIGHT +GLADE_FIXED_CURSOR_LEFT +
+ +
+glade-palette +GladePalette +GladePalette +GladeItemAppearance +glade_palette_new +glade_palette_get_item_appearance +glade_palette_set_item_appearance +glade_palette_get_use_small_item_icons +glade_palette_set_use_small_item_icons +glade_palette_set_show_selector_button +glade_palette_get_show_selector_button +glade_palette_set_project +glade_palette_get_project +glade_palette_get_tool_palette + +GLADE_PALETTE +GLADE_IS_PALETTE +GLADE_TYPE_PALETTE +glade_palette_get_type +GLADE_PALETTE_CLASS +GLADE_IS_PALETTE_CLASS +GLADE_PALETTE_GET_CLASS +GladePaletteClass +GladePalettePrivate +glade_item_appearance_get_type +GLADE_TYPE_ITEM_APPEARANCE +
+ +
+glade-placeholder +GladePlaceholder +GladePlaceholder +glade_placeholder_new +glade_placeholder_get_parent +glade_placeholder_get_project +glade_placeholder_packing_actions + +GLADE_PLACEHOLDER +GLADE_IS_PLACEHOLDER +GLADE_TYPE_PLACEHOLDER +glade_placeholder_get_type +GLADE_PLACEHOLDER_CLASS +GLADE_IS_PLACEHOLDER_CLASS +GLADE_PLACEHOLDER_GET_CLASS +GladePlaceholderClass +GladePlaceholderPrivate +
+ +
+glade-project +GladeProject +GladeProject +GladeProjectModelColumns +GladeProjectProperties +GladeSupportMask +GladePointerMode +GladeVerifyFlags +glade_project_new +glade_project_load_from_file +glade_project_load +glade_project_save +glade_project_get_path +glade_project_get_name +glade_project_undo +glade_project_redo +glade_project_next_undo_item +glade_project_next_redo_item +glade_project_push_undo +glade_project_undo_items +glade_project_redo_items +glade_project_reset_path +glade_project_get_readonly +glade_project_get_objects +glade_project_add_object +glade_project_remove_object +glade_project_has_object +glade_project_get_widget_by_name +glade_project_new_widget_name +glade_project_is_selected +glade_project_selection_set +glade_project_selection_add +glade_project_selection_remove +glade_project_selection_clear +glade_project_selection_changed +glade_project_selection_get +glade_project_get_has_selection +glade_project_resource_fullpath +glade_project_is_loading +glade_project_get_file_mtime +glade_project_get_modified +glade_project_autosave +glade_project_available_widget_name +glade_project_backup +glade_project_cancel_load +glade_project_check_reordered +glade_project_command_cut +glade_project_command_delete +glade_project_command_paste +glade_project_copy_selection +glade_project_display_dependencies +glade_project_get_add_item +glade_project_get_css_provider_path +glade_project_get_license +glade_project_get_pointer_mode +glade_project_get_resource_path +glade_project_get_target_version +glade_project_get_template +glade_project_get_translation_domain +glade_project_load_cancelled +glade_project_preview +glade_project_properties +glade_project_push_progress +glade_project_queue_selection_changed +glade_project_required_libs +glade_project_save_verify +glade_project_set_add_item +glade_project_set_css_provider_path +glade_project_set_license +glade_project_set_pointer_mode +glade_project_set_resource_path +glade_project_set_target_version +glade_project_set_template +glade_project_set_translation_domain +glade_project_set_widget_name +glade_project_toplevels +glade_project_verify +glade_project_verify_property +glade_project_verify_signal +glade_project_verify_widget_adaptor +glade_project_widget_changed +glade_project_widget_visibility_changed +glade_project_writing_preview +glade_project_properties_new + +GladeProjectPropertiesClass +GladeProjectPropertiesPrivate +GLADE_PROJECT +GLADE_IS_PROJECT +GLADE_TYPE_PROJECT +glade_project_get_type +GLADE_PROJECT_CLASS +GLADE_IS_PROJECT_CLASS +GLADE_PROJECT_GET_CLASS +GLADE_TYPE_PROJECT_MODEL_COLUMNS +glade_project_model_columns_get_type +GLADE_TYPE_PROJECT_PROPERTIES +glade_project_properties_get_type +GladeProjectClass +GladeProjectPrivate +GLADE_PROJECT_PROPERTIES +GLADE_PROJECT_PROPERTIES_CLASS +GLADE_PROJECT_PROPERTIES_GET_CLASS +GLADE_IS_PROJECT_PROPERTIES +GLADE_IS_PROJECT_PROPERTIES_CLASS +glade_support_mask_get_type +GLADE_TYPE_SUPPORT_MASK +glade_verify_flags_get_type +GLADE_TYPE_VERIFY_FLAGS +_NodeEdge +
+ +
+glade-cell-renderer-icon +GladeCellRendererIcon +GladeCellRendererIcon +glade_cell_renderer_icon_get_activatable +glade_cell_renderer_icon_get_active +glade_cell_renderer_icon_new +glade_cell_renderer_icon_set_activatable +glade_cell_renderer_icon_set_active + +GladeCellRendererIconClass +GladeCellRendererIconPrivate +glade_cell_renderer_icon_get_type +GLADE_CELL_RENDERER_ICON +GLADE_CELL_RENDERER_ICON_CLASS +GLADE_CELL_RENDERER_ICON_GET_CLASS +GLADE_IS_CELL_RENDERER_ICON +GLADE_IS_CELL_RENDERER_ICON_CLASS +GLADE_TYPE_CELL_RENDERER_ICON +
+ +
+glade-base-editor +GladeBaseEditor +GladeBaseEditor +glade_base_editor_new +glade_base_editor_add_default_properties +glade_base_editor_add_properties +glade_base_editor_add_label +glade_base_editor_set_show_signal_editor +glade_base_editor_pack_new_window +glade_base_editor_add_editable +glade_base_editor_append_types + +GLADE_BASE_EDITOR +GLADE_IS_BASE_EDITOR +GLADE_TYPE_BASE_EDITOR +glade_base_editor_get_type +GLADE_BASE_EDITOR_CLASS +GLADE_IS_BASE_EDITOR_CLASS +GLADE_BASE_EDITOR_GET_CLASS +GladeBaseEditorClass +GladeBaseEditorPrivate +
+ +
+glade-inspector +GladeInspector +GladeInspector +glade_inspector_new +glade_inspector_new_with_project +glade_inspector_set_project +glade_inspector_get_project +glade_inspector_get_selected_items + +GLADE_INSPECTOR +GLADE_IS_INSPECTOR +GLADE_TYPE_INSPECTOR +glade_inspector_get_type +GLADE_INSPECTOR_CLASS +GLADE_IS_INSPECTOR_CLASS +GLADE_INSPECTOR_GET_CLASS +GladeInspectorClass +GladeInspectorPrivate +
+ +
+glade-signal-editor +GladeSignalEditor +glade_signal_editor_get_widget +glade_signal_editor_new +glade_signal_editor_load_widget + +GLADE_SIGNAL_EDITOR +GLADE_IS_SIGNAL_EDITOR +GLADE_TYPE_SIGNAL_EDITOR +GLADE_SIGNAL_EDITOR_CLASS +GLADE_IS_SIGNAL_EDITOR_CLASS +GLADE_SIGNAL_EDITOR_GET_CLASS +GladeSignalEditorClass +GladeSignalEditorPrivate +glade_signal_editor_get_type +
+ +
+glade-editable +GladeEditable +GladeEditable +GladeEditableInterface +glade_editable_block +glade_editable_load +glade_editable_loaded_widget +glade_editable_loading +glade_editable_set_show_name +glade_editable_unblock + +glade_editable_get_type +GLADE_TYPE_EDITABLE +GLADE_EDITABLE +GLADE_EDITABLE_CLASS +GLADE_EDITABLE_GET_IFACE +GLADE_IS_EDITABLE +
+ +
+glade-editor +GladeEditor +GladeEditor +GladeEditorTable +GladeEditorPageType +GladeEditorSkeleton +glade_editor_new +glade_editor_load_widget +glade_editor_query_dialog +glade_editor_show_info +glade_editor_hide_info +glade_editor_dialog_for_widget +glade_editor_hide_class_field +glade_editor_reset_dialog_run +glade_editor_show_class_field +glade_editor_skeleton_new +glade_editor_skeleton_add_editor +glade_editor_table_new + +GLADE_EDITOR +GLADE_IS_EDITOR +GLADE_TYPE_EDITOR +glade_editor_get_type +GLADE_EDITOR_CLASS +GLADE_IS_EDITOR_CLASS +GLADE_EDITOR_GET_CLASS +GLADE_EDITOR_TABLE +GLADE_IS_EDITOR_TABLE +glade_editor_table_get_type +GLADE_IS_EDITOR_TABLE_CLASS +glade_editor_skeleton_get_type +GladeEditorClass +GladeEditorTableClass +GladeEditorTablePrivate +GladeEditorPrivate +GladeEditorPropertyPrivate +GladeEditorSkeletonClass +GladeEditorSkeletonPrivate +glade_editor_page_type_get_type +GLADE_TYPE_EDITOR_PAGE_TYPE +GLADE_TYPE_EDITOR_SKELETON +GLADE_TYPE_EDITOR_TABLE +GLADE_IS_EDITOR_SKELETON +GLADE_IS_EDITOR_SKELETON_CLASS +GLADE_EDITOR_SKELETON +GLADE_EDITOR_SKELETON_CLASS +GLADE_EDITOR_SKELETON_GET_CLASS +GLADE_EDITOR_TABLE_CLASS +GLADE_EDITOR_TABLE_GET_CLASS +
+ +
+glade-app +GladeApp +GladeApp +glade_app_new +glade_pointer_mode_get_type +glade_app_get +glade_app_set_window +glade_app_get_window +glade_app_get_clipboard +glade_app_add_project +glade_app_remove_project +glade_app_get_projects +glade_app_get_config +glade_app_is_project_loaded +glade_app_get_project_by_path +glade_app_config_save +glade_app_set_accel_group +glade_app_get_catalogs_dir +glade_app_get_modules_dir +glade_app_get_pixmaps_dir +glade_app_get_locale_dir +glade_app_do_event +glade_app_get_accel_group +glade_app_get_bin_dir +glade_app_get_catalog +glade_app_get_catalog_version +glade_app_get_catalogs +glade_app_get_lib_dir +glade_app_search_docs + +GLADE_APP +GLADE_IS_APP +GLADE_TYPE_APP +glade_app_get_type +GLADE_APP_CLASS +GLADE_IS_APP_CLASS +GLADE_APP_GET_CLASS +GladeAppClass +GLADE_TYPE_POINTER_MODE +GladeAppPrivate +GLADE_ENV_CATALOG_PATH +GLADE_ENV_MODULE_PATH +GLADE_ENV_BUNDLED +GLADE_ENV_ICON_THEME_PATH +GLADE_ENV_PIXMAP_DIR +GLADE_ENV_TESTING +
+ +
+glade-property +GladeProperty +GladePropertyState +glade_property_new +glade_property_dup +glade_property_reset +glade_property_original_reset +glade_property_default +glade_property_original_default +glade_property_equals_value +glade_property_equals +glade_property_set_value +glade_property_set_va_list +glade_property_set +glade_property_get_value +glade_property_get_default +glade_property_get_va_list +glade_property_get +glade_property_add_object +glade_property_remove_object +glade_property_sync +glade_property_load +glade_property_read +glade_property_write +glade_property_set_sensitive +glade_property_get_sensitive +glade_property_set_save_always +glade_property_get_save_always +glade_property_set_enabled +glade_property_get_enabled +glade_property_i18n_set_comment +glade_property_i18n_get_comment +glade_property_i18n_set_translatable +glade_property_i18n_get_translatable +glade_property_push_superuser +glade_property_pop_superuser +glade_property_superuser +glade_property_make_string +glade_property_set_support_warning +glade_property_set_widget +glade_property_get_def +glade_property_get_state +glade_property_get_support_warning +glade_property_get_widget +glade_property_i18n_get_context +glade_property_i18n_set_context +glade_property_inline_value +glade_propert_get_insensitive_tooltip +glade_property_warn_usage + +GLADE_PROPERTY +GLADE_IS_PROPERTY +GLADE_TYPE_PROPERTY +glade_property_get_type +GLADE_PROPERTY_CLASS +GLADE_IS_PROPERTY_CLASS +GLADE_PROPERTY_GET_CLASS +GladePropertyClass +GladePropertyPrivate +glade_property_state_get_type +GLADE_TYPE_PROPERTY_STATE +
+ +
+glade-property-label +GladePropertyLabel +GladePropertyLabel +glade_property_label_new +glade_property_label_set_append_colon +glade_property_label_get_append_colon +glade_property_label_set_custom_text +glade_property_label_get_custom_text +glade_property_label_set_custom_tooltip +glade_property_label_get_custom_tooltip +glade_property_label_set_packing +glade_property_label_get_packing +glade_property_label_set_property +glade_property_label_get_property +glade_property_label_set_property_name +glade_property_label_get_property_name + +GladePropertyLabelClass +GladePropertyLabelPrivate +glade_property_label_get_type +GLADE_PROPERTY_LABEL +GLADE_PROPERTY_LABEL_CLASS +GLADE_PROPERTY_LABEL_GET_CLASS +GLADE_TYPE_PROPERTY_LABEL +GLADE_IS_PROPERTY_LABEL +GLADE_IS_PROPERTY_LABEL_CLASS +
+ +
+glade-property-shell +GladePropertyShell +GladePropertyShell +glade_property_shell_new +glade_property_shell_get_custom_text +glade_property_shell_get_disable_check +glade_property_shell_get_packing +glade_property_shell_get_property_name +glade_property_shell_get_use_command +glade_property_shell_set_custom_text +glade_property_shell_set_disable_check +glade_property_shell_set_packing +glade_property_shell_set_property_name +glade_property_shell_set_use_command + +GladePropertyShellClass +GladePropertyShellPrivate +glade_property_shell_get_type +GLADE_PROPERTY_SHELL +GLADE_PROPERTY_SHELL_CLASS +GLADE_PROPERTY_SHELL_GET_CLASS +GLADE_TYPE_PROPERTY_SHELL +GLADE_IS_PROPERTY_SHELL +GLADE_IS_PROPERTY_SHELL_CLASS +
+ +
+glade-previewer +GladePreviewer +GladePreviewer +glade_previewer_connect_function +glade_previewer_new +glade_previewer_present +glade_previewer_screenshot +glade_previewer_set_css_file +glade_previewer_set_message +glade_previewer_set_print_handlers +glade_previewer_set_screenshot_extension +glade_previewer_set_slideshow_widgets +glade_previewer_set_widget +glade_previewer_slideshow_save + +GLADE_TYPE_PREVIEWER +GLADE_PREVIEWER +GLADE_PREVIEWER_CLASS +GLADE_PREVIEWER_GET_CLASS +GLADE_IS_PREVIEWER +GLADE_IS_PREVIEWER_CLASS +glade_previewer_get_type +GladePreviewerClass +GladePreviewerPrivate +
+ +
+glade-command +GladeCommand +GladeCommandSetPropData +GladeCommand +glade_command_push_group +glade_command_pop_group +glade_command_execute +glade_command_undo +glade_command_unifies +glade_command_collapse +glade_command_set_property +glade_command_set_property_value +glade_command_set_properties +glade_command_set_properties_list +glade_command_set_name +glade_command_delete +glade_command_create +glade_command_cut +glade_command_paste +glade_command_dnd +glade_command_add_signal +glade_command_remove_signal +glade_command_change_signal +glade_command_set_i18n +glade_command_add +glade_command_description +glade_command_get_group_depth +glade_command_group_id +glade_command_lock_widget +glade_command_unlock_widget +glade_command_set_project_domain +glade_command_set_project_license +glade_command_set_project_resource_path +glade_command_set_project_target +glade_command_set_project_template +glade_command_set_property_enabled + +GLADE_COMMAND +GLADE_IS_COMMAND +GLADE_TYPE_COMMAND +glade_command_get_type +GLADE_COMMAND_CLASS +GLADE_IS_COMMAND_CLASS +GLADE_COMMAND_GET_CLASS +GladeCommandClass +GladeCommandPrivate +
+ +
+glade-utils +GladeUIMessageType +GladeUtilFileDialogType +glade_util_get_type_from_name +glade_utils_get_pspec_from_funcname +glade_util_ui_message +glade_util_flash_message +glade_util_compare_stock_labels +glade_util_file_dialog_new +glade_util_replace +glade_util_read_prop_name +glade_util_duplicate_underscores +glade_util_container_get_all_children +glade_util_count_placeholders +glade_util_find_iter_by_widget +glade_util_purify_list +glade_util_added_in_list +glade_util_removed_from_list +glade_util_canonical_path +glade_util_load_library +glade_util_file_is_writeable +glade_util_have_devhelp +glade_util_get_devhelp_icon +glade_util_search_devhelp +glade_util_get_placeholder_from_pointer +glade_util_object_is_loading +glade_util_url_show +glade_util_get_file_mtime +glade_util_check_and_warn_scrollable +glade_util_filename_to_icon_name +glade_util_icon_name_to_filename +glade_util_remove_scroll_events +glade_utils_boolean_from_string +glade_utils_cairo_draw_line +glade_utils_cairo_draw_rectangle +glade_utils_enum_string_from_value +glade_utils_enum_string_from_value_displayable +glade_utils_enum_value_from_string +glade_utils_flags_string_from_value +glade_utils_flags_string_from_value_displayable +glade_utils_flags_value_from_string +glade_utils_get_pointer +glade_utils_hijack_key_press +glade_utils_liststore_from_enum_type +glade_utils_pointer_mode_render_icon +glade_utils_replace_home_dir_with_tilde +glade_utils_string_from_value +glade_utils_value_from_string +glade_get_displayable_value +glade_get_value_from_displayable +glade_type_has_displayable_values +glade_register_displayable_value +glade_register_translated_value +glade_displayable_value_is_disabled +glade_displayable_value_set_disabled +glade_path +glade_path_HEIGHT +glade_path_WIDTH + +glade_ui_message_type_get_type +glade_util_file_dialog_type_get_type +GLADE_TYPE_UI_MESSAGE_TYPE +GLADE_TYPE_UTIL_FILE_DIALOG_TYPE +GWA_VERSION_CHECK +GWA_VERSION_SINCE_MAJOR +GWA_VERSION_SINCE_MINOR +GLADE_PROPERTY_DEF_VERSION_CHECK +GSC_VERSION_CHECK +GWA_DEPRECATED +GWA_DEPRECATED_SINCE_CHECK +GWA_DEPRECATED_SINCE_MAJOR +GWA_DEPRECATED_SINCE_MINOR +GLADE_DEVHELP_FALLBACK_ICON_FILE +GLADE_DEVHELP_ICON_NAME +GLADE_GTKBUILDER_HAS_VERSIONING +GLADE_GTKBUILDER_VERSIONING_BASE_MAJOR +GLADE_GTKBUILDER_VERSIONING_BASE_MINOR +
+ +
+glade-xml-utils +glade_xml_load_sym_from_node +glade_xml_dump_from_context +glade_xml_get_property_targetable_versions +glade_xml_get_property_version +glade_xml_search_child +glade_xml_search_child_required +glade_xml_get_content +glade_xml_set_content +glade_xml_get_value_int +glade_xml_get_value_int_required +glade_xml_get_value_string +glade_xml_get_value_string_required +glade_xml_get_boolean +glade_xml_set_value +glade_xml_get_property_string_required +glade_xml_get_property_string +glade_xml_get_property_boolean +glade_xml_get_property_double +glade_xml_get_property_int +glade_xml_node_set_property_string +glade_xml_node_set_property_boolean +glade_xml_node_new +glade_xml_node_new_comment +glade_xml_node_copy +glade_xml_node_delete +glade_xml_node_get_children +glade_xml_node_next +glade_xml_node_verify +glade_xml_node_verify_silent +glade_xml_node_get_name +glade_xml_node_get_parent +glade_xml_node_append_child +glade_xml_node_remove +glade_xml_node_is_comment +glade_xml_node_next_with_comments +glade_xml_node_get_children_with_comments +glade_xml_node_add_next_sibling +glade_xml_node_add_prev_sibling +glade_xml_node_prev_with_comments +glade_xml_doc_new +glade_xml_doc_new_comment +glade_xml_doc_ref +glade_xml_doc_unref +glade_xml_doc_get_root +glade_xml_doc_set_root +glade_xml_doc_save +glade_xml_context_new +glade_xml_context_copy +glade_xml_context_free +glade_xml_context_new_from_path +glade_xml_context_get_doc +GladeXmlContext +GladeXmlNode +GladeXmlDoc + +glade_xml_context_get_type +glade_xml_doc_get_type +glade_xml_node_get_type +GLADE_XML_CONTEXT +GLADE_XML_IS_CONTEXT +CAST_BAD +GLADE_XML_TAG_PROJECT +GLADE_XML_TAG_WIDGET +GLADE_XML_TAG_LIBGLADE_PROJECT +GLADE_XML_TAG_BUILDER_PROJECT +GLADE_XML_TAG_LIBGLADE_WIDGET +GLADE_XML_TAG_BUILDER_WIDGET +GLADE_XML_TAG_REQUIRES +GLADE_XML_TAG_LIB +GLADE_XML_TAG_PROPERTY +GLADE_XML_TAG_CLASS +GLADE_XML_TAG_ID +GLADE_XML_TAG_SIGNAL +GLADE_XML_TAG_HANDLER +GLADE_XML_TAG_AFTER +GLADE_XML_TAG_OBJECT +GLADE_XML_TAG_NAME +GLADE_XML_TAG_CHILD +GLADE_XML_TAG_PACKING +GLADE_XML_TAG_PLACEHOLDER +GLADE_XML_TAG_INTERNAL_CHILD +GLADE_XML_TAG_I18N_TRUE +GLADE_XML_TAG_SIGNAL_TRUE +GLADE_XML_TAG_TYPE +GLADE_XML_TAG_FILENAME +GLADE_XML_TAG_SIGNAL_FALSE +GLADE_XML_TAG_SOURCE +GLADE_XML_TAG_SOURCES +GLADE_XML_TAG_STOCK_ID +GLADE_XML_TAG_SWAPPED +GLADE_XML_TAG_TEMPLATE +GLADE_XML_TAG_VERSION +GLADE_TAG_ACTION +GLADE_TAG_ACTION_ACTIVATE_FUNCTION +GLADE_TAG_ACTIONS +GLADE_TAG_ACTION_SUBMENU_FUNCTION +GLADE_TAG_ADAPTOR +GLADE_TAG_ADD_CHILD_FUNCTION +GLADE_TAG_ADD_CHILD_VERIFY_FUNCTION +GLADE_TAG_ANARCHIST +GLADE_TAG_ATK_PROPERTY +GLADE_TAG_BIND_FLAGS +GLADE_TAG_BIND_PROPERTY +GLADE_TAG_BIND_SOURCE +GLADE_TAG_BOOK +GLADE_TAG_BUILDER_SINCE +GLADE_TAG_CHILD_ACTION_ACTIVATE_FUNCTION +GLADE_TAG_CHILD_GET_PROP_FUNCTION +GLADE_TAG_CHILD_PROPERTY +GLADE_TAG_CHILD_SET_PROP_FUNCTION +GLADE_TAG_CHILD_VERIFY_FUNCTION +GLADE_TAG_COMMENT +GLADE_TAG_COMMON +GLADE_TAG_CONSTRUCT_OBJECT_FUNCTION +GLADE_TAG_CONSTRUCT_ONLY +GLADE_TAG_CONSTRUCTOR_FUNCTION +GLADE_TAG_CONTEXT +GLADE_TAG_CREATE_EDITABLE_FUNCTION +GLADE_TAG_CREATE_EPROP_FUNCTION +GLADE_TAG_CREATE_TYPE +GLADE_TAG_CREATE_WIDGET_FUNCTION +GLADE_TAG_CUSTOM_LAYOUT +GLADE_TAG_DEEP_POST_CREATE_FUNCTION +GLADE_TAG_DEFAULT +GLADE_TAG_DEFAULT_HEIGHT +GLADE_TAG_DEFAULT_PALETTE_STATE +GLADE_TAG_DEFAULT_WIDTH +GLADE_TAG_DEPENDS +GLADE_TAG_DEPENDS_FUNCTION +GLADE_TAG_DEPRECATED +GLADE_TAG_DEPRECATED_SINCE +GLADE_TAG_DESTROY_OBJECT_FUNCTION +GLADE_TAG_DISABLED +GLADE_TAG_DISPLAYABLE_VALUES +GLADE_TAG_DOMAIN +GLADE_TAG_EDITABLE +GLADE_TAG_EVENT_HANDLER_CONNECTED +GLADE_TAG_EXPANDED +GLADE_TAG_FALSE +GLADE_TAG_FIXED +GLADE_TAG_GENERIC_NAME +GLADE_TAG_GET_CHILDREN_FUNCTION +GLADE_TAG_GET_FUNCTION +GLADE_TAG_GET_INTERNAL_CHILD_FUNCTION +GLADE_TAG_GET_TYPE_FUNCTION +GLADE_TAG_GLADE_CATALOG +GLADE_TAG_GLADE_WIDGET_CLASS +GLADE_TAG_GLADE_WIDGET_CLASSES +GLADE_TAG_GLADE_WIDGET_CLASS_REF +GLADE_TAG_GLADE_WIDGET_GROUP +GLADE_TAG_HAS_CONTEXT +GLADE_TAG_ICON_NAME +GLADE_TAG_ICON_PREFIX +GLADE_TAG_ID +GLADE_TAG_IGNORE +GLADE_TAG_IMPORTANT +GLADE_TAG_INIT_FUNCTION +GLADE_TAG_INTERNAL_CHILDREN +GLADE_TAG_KEY +GLADE_TAG_LIBRARY +GLADE_TAG_MAX_VALUE +GLADE_TAG_MIN_VALUE +GLADE_TAG_MULTILINE +GLADE_TAG_NAME +GLADE_TAG_NEEDS_SYNC +GLADE_TAG_NICK +GLADE_TAG_NO +GLADE_TAG_OPTIONAL +GLADE_TAG_OPTIONAL_DEFAULT +GLADE_TAG_PACKING_ACTIONS +GLADE_TAG_PACKING_DEFAULTS +GLADE_TAG_PACKING_PROPERTIES +GLADE_TAG_PARAMETER +GLADE_TAG_PARAMETERS +GLADE_TAG_PARENT +GLADE_TAG_PARENT_CLASS +GLADE_TAG_PARENTLESS_WIDGET +GLADE_TAG_POST_CREATE_FUNCTION +GLADE_TAG_PROPERTIES +GLADE_TAG_PROPERTY +GLADE_TAG_QUERY +GLADE_TAG_READ_CHILD_FUNCTION +GLADE_TAG_READ_WIDGET_FUNCTION +GLADE_TAG_REMOVE_CHILD_FUNCTION +GLADE_TAG_REPLACE_CHILD_FUNCTION +GLADE_TAG_RESOURCE +GLADE_TAG_SAVE +GLADE_TAG_SAVE_ALWAYS +GLADE_TAG_SET_FUNCTION +GLADE_TAG_SIGNAL +GLADE_TAG_SIGNAL_NAME +GLADE_TAG_SIGNALS +GLADE_TAG_SPEC +GLADE_TAG_SPECIAL_CHILD_TYPE +GLADE_TAG_SPECIFICATIONS +GLADE_TAG_STOCK +GLADE_TAG_STOCK_ICON +GLADE_TAG_STRING_FROM_VALUE_FUNCTION +GLADE_TAG_TARGETABLE +GLADE_TAG_TEMPLATE_PREFIX +GLADE_TAG_THEMED_ICON +GLADE_TAG_TITLE +GLADE_TAG_TOOLTIP +GLADE_TAG_TOPLEVEL +GLADE_TAG_TRANSFER_ON_PASTE +GLADE_TAG_TRANSLATABLE +GLADE_TAG_TRUE +GLADE_TAG_TYPE +GLADE_TAG_USE_PLACEHOLDERS +GLADE_TAG_VALUE +GLADE_TAG_VALUE_TYPE +GLADE_TAG_VERIFY_FUNCTION +GLADE_TAG_VERSION +GLADE_TAG_VERSION_SINCE +GLADE_TAG_VISIBLE +GLADE_TAG_VISIBLE_LINES +GLADE_TAG_WEIGHT +GLADE_TAG_WRITE_CHILD_FUNCTION +GLADE_TAG_WRITE_WIDGET_AFTER_FUNCTION +GLADE_TAG_WRITE_WIDGET_FUNCTION +GLADE_TAG_YES +GLADE_ENUM_DATA_TAG +
+ +
+glade-widget-action +GladeWidgetAction +GladeWidgetAction +glade_widget_action_get_children +glade_widget_action_get_def +glade_widget_action_set_sensitive +glade_widget_action_get_sensitive +glade_widget_action_get_visible +glade_widget_action_set_visible + +GLADE_WIDGET_ACTION +GLADE_IS_WIDGET_ACTION +GLADE_TYPE_WIDGET_ACTION +glade_widget_action_get_type +GLADE_WIDGET_ACTION_CLASS +GLADE_IS_WIDGET_ACTION_CLASS +GLADE_WIDGET_ACTION_GET_CLASS +GladeWidgetActionPrivate +
+ +
+glade-widget-action-def +GladeWidgetActionDef +GladeWidgetActionDef +glade_widget_action_def_new +glade_widget_action_def_free +glade_widget_action_def_clone +glade_widget_action_def_set_important +glade_widget_action_def_set_label +glade_widget_action_def_set_stock + +GLADE_TYPE_WIDGET_ACTION_DEF +GLADE_WIDGET_ACTION_DEF +glade_widget_action_def_get_type +
+ +
+glade-preview +GladePreview +GladePreview +glade_preview_get_pid +glade_preview_get_widget +glade_preview_launch +glade_preview_template_object_new +glade_preview_update +QUIT_TOKEN +QUIT_TOKEN_SIZE +UPDATE_TOKEN +UPDATE_TOKEN_SIZE + +GladePreviewClass +GladePreviewPrivate +GLADE_PREVIEW +GLADE_IS_PREVIEW +GLADE_PREVIEW_CLASS +GLADE_IS_PREVIEW_CLASS +GLADE_PREVIEW_GET_CLASS +glade_preview_get_type +GLADE_TYPE_PREVIEW +
diff --git a/doc/gladeui.types b/doc/gladeui.types new file mode 100644 index 0000000..cc54668 --- /dev/null +++ b/doc/gladeui.types @@ -0,0 +1,18 @@ +#include + +glade_app_get_type +glade_clipboard_get_type +glade_command_get_type +glade_editor_get_type +glade_editor_property_get_type +glade_palette_get_type +glade_placeholder_get_type +glade_project_get_type +glade_inspector_get_type +glade_property_get_type +glade_signal_get_type +glade_widget_get_type +glade_widget_adaptor_get_type +glade_design_view_get_type +glade_base_editor_get_type +glade_widget_action_get_type diff --git a/doc/meson.build b/doc/meson.build new file mode 100644 index 0000000..36da1ff --- /dev/null +++ b/doc/meson.build @@ -0,0 +1,49 @@ +private_doc_headers = [ + 'glade-builtins.h', + 'glade-debug.h', + 'glade-design-private.h', + 'glade-drag.h', + 'glade-accumulators.h', + 'glade-marshallers.h', + 'glade-paths.h', + 'glade-custom.h', + 'glade-cursor.h', + 'glade-id-allocator.h', + 'glade.h', + 'glade-design-layout.h', + 'glade-popup.h', + 'glade-gtk.h', + 'glade-palette-expander.h', + 'glade-palette-item.h', + 'glade-named-icon-chooser-dialog.h', + 'glade-palette-box.h', + 'glade-private.h', +] + +content_files = files( + 'catalogintro.sgml', + 'gladepython.sgml', + 'properties.sgml', + 'widgetclasses.sgml', +) + +version_conf = configuration_data() +version_conf.set('PACKAGE_STRING', '@0@ @1@'.format(glade_name, glade_version)) + +content_files += configure_file( + input: 'version.xml.in', + output: '@BASENAME@', + configuration: version_conf, +) + +gnome.gtkdoc( + gladeui_name, + main_xml: gladeui_name + '-docs.xml', + module_version: gladeui_major_version.to_string(), + src_dir: gladeui_inc, + ignore_headers: private_doc_headers, + include_directories: top_inc, + dependencies: libgladeui_dep, + content_files: content_files, + install: true, +) diff --git a/doc/properties.sgml b/doc/properties.sgml new file mode 100644 index 0000000..bc73207 --- /dev/null +++ b/doc/properties.sgml @@ -0,0 +1,272 @@ + + + Property Definitions + Glade UI + + + Property Definitions + +How to augment or define a #GladePropertyDef + + + + + Property Definition Configuration + +Properties are introspected at load time and loaded into #GladePropertyDef structures. +The behaviour of properties can be modified by the catalog and fake properties can be added +for editing purposes. Here is an example of the xml form: + + + ... spec, tooltip etc + + + + + + + ... values here + + + + +...]]> + + + + +Properties of the 'property' tag: + + + id + + +This is mandatory and specifies the property that we are modifying (or adding) + + + + + + name + + +The name to be used in the interface. +(if name is not specified; it defaults to the nickname of the #GParamSpec) + + + + + + since + + +A 'major.minor' formed version describing the version of the owning catalog in which this +property was introduced; example: since="1.0". Properties are initialized +to be supported since the introducing #GladeWidgetAdaptor was supported. + + + + + + disabled + + +Remove this property from this widget class and derived classes + + + + + + default + + +A default value to be used for this property + + + + + + translatable + + +For text properties, whether the property value is translatable in glade +interfaces (this will enable the i18n dialog on text properties). Defaults to False. + + + + + + common + + +If set to "True", the property will end up on the common tab even if +its not a property of GtkWidgetClass. + + + + + + optional + + +Whether this property is an optional property, this will make the property +insensitive and add a check box to enable it (like width/height-request for +example). + + + + + + optional-default + + +If this is in fact an optional property; whether it is enabled by default. + + + + + + query + + +If query is set; the property will be queried from the user in a dialog +when adding the owning widget class instance to the project. + + + + + + save + + +Whether to save this property to the glade file (default "True") + + + + + + visible + + +Whether to show the property in the editor and reset dialog (default "True") + + + + + + custom-layout + + +This is used to avoid loading this property in the editor when implementing +a custom #GladeEditable that embeds the base #GladeEditorTable implementation, +custom-layout properties will still show up in the reset dialog (default "False) + + + + + + ignore + + +Whether to set the property on the object instance (via g_object_set_property or +plugin override functions) when it changes in the editor (the value in the editor +is the value saved). + + + + + + themed-icon + + +Depicts a string property that is used for an icon from the theme. These will +the appropriate editor. + + + + + + weight + + +A numerical value to specify this properties position in the property editor. + + + + + + transfer-on-paste + + +Used for packing properties; depicts packing properties that should follow +the widget when pasted to a new container that supports the same properties. + + + + + + save-always + + +Specifies that the property should be saved regardless of its value (properties at their +default values are normally not saved). + + + + + + + +Child tags of the 'property' tag: + + + + spec + + +Specifies a function to be used to return a #GParamSpec for this property; +this is used to add virtual properties to an object (like the "size" property +on #GtkBox). + + + + + + tooltip + + +The tooltip to be displayed in the property editor for this property. +The tooltip defaults to the blurb of the associated #GParamSpec. + + + + + + visible-lines + + +An integer value to specify how many lines will be shown for text properties +in the editor (this doesnt really work because of the complexity of calculating +size of rendered text; instead, just set this to 2 if you want the text property +to be edited in a textview with a scrolled window as opposed to a simple text entry). + + + + + + displayable-values + + +Allows you to specify user friendly strings for enum and flag values as shown in the +example above, use the `id' property in the value tag to depict the real value name +and the `name' property for the human readable one. + + + + + + + + diff --git a/doc/version.xml.in b/doc/version.xml.in new file mode 100644 index 0000000..10032a7 --- /dev/null +++ b/doc/version.xml.in @@ -0,0 +1 @@ +@PACKAGE_STRING@ \ No newline at end of file diff --git a/doc/widgetclasses.sgml b/doc/widgetclasses.sgml new file mode 100644 index 0000000..27fc8f7 --- /dev/null +++ b/doc/widgetclasses.sgml @@ -0,0 +1,555 @@ + + + Widget Adaptor Definitions + Glade UI + + + Widget Classes + +Adding support for custom widgets + + + + + Forward + + +Widget support in Glade is provided by the #GladeWidgetAdaptor object, each +`glade-widget-class' definition in the catalog will result in the creation +of an adaptor, which inherits the functionality of the adaptor created for its +parent, or the closest adaptor available in the ancestry. Adaptor methods can +be overridden by functions in the plugin by specifying them in the catalog. +There is also a wealth of options you can use in the `glade-widget-class' without +writing code. + + +A typical basic example of a widget class definition is as follows: + +]]> + + +Here is the complete example of the form of the `glade-widget-class' definition: + + + foo_frobnicator_plugin_post_create + + ... widget class support functions go here + + + + + ... property definitions go here + + + + + + ... signal definitions go here + + + + ... child packing property definitions go here + + + + + + ... context menu action definitions go here + + + + ... context menu action definitions for child objects go here + + + + + + + + ... packing default definitions go here + +]]> + + + + + + Widget Class Properties + +The following are all properties of the `glade-widget-class' tag + + + + name + + +The class name of the widget; unless the 'get-type-function' property is present, +this will essentially be used to instantiate the actual class by deriving +'gtk_label_get_type' from 'GtkLabel' and searching for 'gtk_label_get_type' in the +support library. + + + + + + since + + +A 'major.minor' formed version describing the version of the owning catalog in which this +object class was introduced; example: since="1.0". + + + + + + deprecated + + +A boolean property depicting that this widget is currently deprecated. + + + + + + builder-unsupported + + +A boolean property depicting that this widget is not supported by #GtkBuilder. + + + + + + get-type-function + + +Used to explicitly specify the name of the function used to get the type of the widget. +It is optional, but if it is not present, the 'name' property will be used to guess the +name of the function, a process that could lead to unexpected results. + + + + + + generic-name + + +Used to generate a default name for instances of the +widget in the UI editor. It is also used in conjunction with the 'icon-prefix' +to form an icon name for the widget. + + + + + + icon-name + + +Used to explicitly set an icon name for this widget. +These icon names are looked up in the current icon theme to retrieve an icon for +the widget. + + + + + + title + + +Used to display the name of the class in the palette and widget +tree and will be translated before use in the interface. + + + + + + parent + + +Use this to specify the name of the parent your widget derives from, you +can use this option instead of loading a real widget type (this allows you +to fake derive and still add properties and run your catalog independently +of any plugins or libraries). + + + + + + toplevel + + +Used to know whether this widget class is toplevel or not +in Glade context. This property will be inherited from the adaptors parent. + + + + + + fixed + + +Used to mark a #GladeWidgetAdaptor for free form placement capacities +to be handled with a #GladeFixed. This property will be inherited from the adaptors parent. + + + + + + use-placeholders + + +Used to inform the core about adaptors that implement +#GtkContainer editing, and whether or not they use #GladePlaceholder. This property will +be inherited from the adaptors parent. + + + + + + default-width + + +The default width to load a #GtkWindow derived widget in the #GladeDesignView. +This property will be inherited from the adaptors parent. + + + + + + default-height + + +The default height to load a #GtkWindow derived widget in the #GladeDesignView. +This property will be inherited from the adaptors parent. + + + + + + + + + Adapter Methods + +The following are all child tags of the `glade-widget-class' tag + + + + constructor-function + + +Used to override the actual #GObject constructor of the said #GladeWidgetAdaptor + + + + + + post-create-function + + +A #GladePostCreateFunc support function that gets called whenever a widget of the said class is instantiated. + + + + + + deep-post-create-function + + +Same as `post-create-function' except in that you must always chain up +to the super class definition as a rule. + + + + + + get-property-function + + +A #GladeGetPropertyFunc to get values of properties on an instance in the +runtime. + + + + + + set-property-function + + +A #GladeSetPropertyFunc to set values of properties on an instance in the +runtime. + + + + + + verify-function + + +A #GladeVerifyPropertyFunc to set values of properties on an instance in the +runtime. + + + + + + add-child-function + + +A #GladeAddChildFunc to add children to instances in the runtime. + + + + + + remove-child-function + + +A #GladeRemoveChildFunc to remove children from instances in the runtime. + + + + + + replace-child-function + + +A #GladeReplaceChildFunc to replace children inside instances in the runtime. + + + + + + get-children-function + + +A #GladeGetChildrenFunc to get a list of children inside instances in the runtime. + + + + + + child-get-property-function + + +A #GladeChildGetPropertyFunc to get packing properties from instances in the runtime. + + + + + + child-set-property-function + + +A #GladeChildSetPropertyFunc to set packing properties on instances in the runtime. + + + + + + child-verify-function + + +A #GladeChildVerifyPropertyFunc to verify packing properties on instances in the runtime. + + + + + + get-internal-child-function + + +A #GladeGetInternalFunc to retrieve an internal child of a composite object in the runtime. + + + + + + action-activate-function + + +A #GladeActionActivateFunc to run a routine for a plugin defined #GladeWidgetAction. + + + + + + child-action-activate-function + + +A #GladeChildActionActivateFunc to run a routine for a plugin defined packing #GladeWidgetAction. + + + + + + read-widget-function + + +A #GladeReadWidgetFunc to load a widget from the xml. + + + + + + write-widget-function + + +A #GladeWriteWidgetFunc to write a widget from the xml. + + + + + + read-child-function + + +A #GladeReadWidgetFunc to read a child to the xml. + + + + + + write-child-function + + +A #GladeWriteWidgetFunc to write a child to the xml. + + + + + + create-editor-property-function + + +A #GladeCreateEPropFunc to create widgets to be used in Glade's property editor. + + + + + + string-from-value-function + + +A #GladeStringFromValueFunc to create a string from a #GValue, these strings must +be unique and comparable for undo/redo to work properly. + + + + + + + + + Action Definitions + +Actions are added under the `actions' and `packing-actions' tags, actions can also +be nested so as to allow grouping in context menus, example: + + + + + + +...]]> + + +Here are the meanings of the `action' tag's properties: + + + + id + + +The string identifier for this action, the action_path argument to your #GladeActionActivateFunc +will be a combination if this id and any parents, for the above example: +"manage_frobs/add_frob". + + + + + + name + + +A string to be displayed in the UI for this action. + + + + + + stock + + +An optional Gtk stock icon to represent this action. + + + + + + important + + +A boolean option to add emphasis to this action; currently actions marked as `important' +are added to the toolbar. + + + + + + + + + Packing Defaults + +Default values can be specified for packing properties on your widget when added +to a said type of parent, as the example shows above; use the `parent-class' to +specify what parent (or derivative of that parent) you are targeting and fill +in any `child-property' tags you need for that parent using `id' property to +specify the property name and the `default' property for the value. + + + + + Signals + +The signal elements are currently only needed for versioning support, use +the `signal' child tag as described above and set signal `id' to be supported +since version `since'. Signals are initialized to be supported since the +introducing #GladeWidgetAdaptor was supported. + + + + + Icons + +The Glade palette and other components use icons to represent the various widget classes. +It is a good idea to provide icons for your widget classes, as this +enhances the user interface. + + + +Glade uses the GTK+ GtkIconTheme facility to provide icons for widget classes. Any icon +available in the current icon theme can be used as an icon for a widget class. + + + +By default, an icon name of the format "widget-CATALOG_NAME-GENERIC_NAME" +is assigned to every widget class. CATALOG_NAME is the value of catalog name attribute, +and GENERIC_NAME is the value of an widget class's generic name attribute. + + + +To explicitly set an icon name for a widget class, the "icon-name" attribute of the +"glade-widget-class" element can be specified. This will override the default icon +name assigned to the widget class. + + + +Icon files can be installed under any system icon theme folder. + + + + diff --git a/glade.doap b/glade.doap new file mode 100644 index 0000000..8ac7ac2 --- /dev/null +++ b/glade.doap @@ -0,0 +1,40 @@ + + + + Glade Interface Designer + Glade is a RAD tool to enable quick and easy development of user interfaces for the GTK+ 3 toolkit and the GNOME desktop environment. + User Interface Builder for GTK+ applications + + + + + + + C + + + + Tristan Van Berkom + + tvb + + + + + Juan Pablo Ugarte + + jpu + + + + + Alberto Fanjul Alonso + + albfan + + + diff --git a/gladeui/.gitignore b/gladeui/.gitignore new file mode 100644 index 0000000..8b469b0 --- /dev/null +++ b/gladeui/.gitignore @@ -0,0 +1,6 @@ +/glade-marshallers.c +/glade-marshallers.h +/gladeui.rc + +/glade-previewer +/glade-previewer.rc diff --git a/gladeui/atk.png b/gladeui/atk.png new file mode 100644 index 0000000000000000000000000000000000000000..2c1ace56a12a2501c9072356141a9eb36d62ff98 GIT binary patch literal 914 zcmV;D18w|?P)^qOeHm#I82gx z@7;S{L~AXc^>;ov-*zBm4xyJHr{a(HNV<0l4 z1}56mN|~8ckB)}JL;d<#rI#?HS#NIh%deYuetD&nCULE?n19Fnn!9UoxO{M>r>9tb z`us$$cqGq$$8zU(6QdR7{-bo~GNeh&N1rY3-P~xdHWssE-q)M~7?@~J7rJ{!&p&l8 z*NQE7Hd`1iyzopF5qN)LgP(7u;Nh{;kLPy&T&Oa!Jq^4q{bR2l$EfeeC(q^bg*-{> zFj_EL@#2LOc;J=yuCv=t04Np$N?Evid4A8ucGP%LQ+?%15N0FV2MK>P>KLuiO7ZgS zkJKB#lcw(9)J}|2$mZxPSAyoo%_$>V#tWsA=_C&4;lsC{BQOdjg2$so5D}C>oI1|F zK2K{uAzv&R*<2qtcsG*EcN3=;5#gnGSI|n>+CAXoH_i}-nkV1b+@u@t(F zfOjM2utLCt)&d9)a1OK*0HVO4mBKntY8^^IFEw>w{}u*{gM*z=C+y}v7|BiD1%mtk%x-pBg5y{Zu9B4O%m%k^GGk*jOM{& zmPn^;uHT5oyE)ZZo~>J#)V{mE(te;jpexEy9W3#|!a8Bb5Cxh`iyNFAEYeqsI9BfB z>#N`FIp=DP<=Oh(SFTKUt{g4*RiC{ukyAmIPV9)S$7n@15W2F4G(O*W7(D?N4XJ%*^Ri)o^%dKo3=VF!XYmLR|zcAg8vxu5f ofgbnPjsPgF*KBN;{Quzm7qakF-K$uIzyJUM07*qoM6N<$g1~*Tr2qf` literal 0 HcmV?d00001 diff --git a/gladeui/glade-accumulators.c b/gladeui/glade-accumulators.c new file mode 100644 index 0000000..168ddec --- /dev/null +++ b/gladeui/glade-accumulators.c @@ -0,0 +1,115 @@ +/* + * glade-clipboard.c - An object for handling Cut/Copy/Paste. + * + * Copyright (C) 2005 The GNOME Foundation. + * + * Author(s): + * Tristan Van Berkom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include "config.h" + +#include "glade-accumulators.h" + +#include + + +gboolean +_glade_single_object_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + GObject *object = g_value_get_object (handler_return); + g_value_set_object (return_accu, object); + + return (object == NULL); +} + +gboolean +_glade_integer_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gint retval; + + retval = g_value_get_int (handler_return); + g_value_set_int (return_accu, retval >> 1); + continue_emission = !(retval & 1); + + return continue_emission; +} + +/* From gtkmain.c */ +gboolean +_glade_boolean_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + gboolean continue_emission; + gboolean signal_handled; + + signal_handled = g_value_get_boolean (handler_return); + g_value_set_boolean (return_accu, signal_handled); + continue_emission = !signal_handled; + + return continue_emission; +} + +gboolean +_glade_string_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + const gchar *handler_str; + + g_free ((void *) g_value_get_string (return_accu)); + + handler_str = g_value_get_string (handler_return); + g_value_set_string (return_accu, handler_str); + + return (handler_str == NULL); +} + +gboolean +_glade_strv_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, gpointer dummy) +{ + const gchar **handler_strv; + + handler_strv = g_value_get_boxed (handler_return); + g_value_set_boxed (return_accu, handler_strv); + + return (handler_strv == NULL); +} + +gboolean +_glade_stop_emission_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy) +{ + g_value_copy (handler_return, return_accu); + + return FALSE; +} diff --git a/gladeui/glade-accumulators.h b/gladeui/glade-accumulators.h new file mode 100644 index 0000000..58dc55b --- /dev/null +++ b/gladeui/glade-accumulators.h @@ -0,0 +1,39 @@ +#ifndef __GLADE_ACCUMULATORS_H__ +#define __GLADE_ACCUMULATORS_H__ + +#include + +G_BEGIN_DECLS + +gboolean _glade_single_object_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy); + +gboolean _glade_integer_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy); + +gboolean _glade_boolean_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy); + +gboolean _glade_string_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy); + +gboolean _glade_strv_handled_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy); + +gboolean _glade_stop_emission_accumulator (GSignalInvocationHint *ihint, + GValue *return_accu, + const GValue *handler_return, + gpointer dummy); +G_END_DECLS + +#endif /* __GLADE_ACCUMULATORS_H__ */ diff --git a/gladeui/glade-adaptor-chooser-widget.c b/gladeui/glade-adaptor-chooser-widget.c new file mode 100644 index 0000000..d413d13 --- /dev/null +++ b/gladeui/glade-adaptor-chooser-widget.c @@ -0,0 +1,650 @@ +/* + * glade-adaptor-chooser-widget.c + * + * Copyright (C) 2014-2017 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Juan Pablo Ugarte + */ + +#include "glade-app.h" +#include "glade-private.h" +#include "gladeui-enum-types.h" +#include "glade-adaptor-chooser-widget.h" +#include "glade-dnd.h" + +#include + +enum +{ + COLUMN_ADAPTOR = 0, + COLUMN_GROUP, + COLUMN_NORMALIZED_NAME, + COLUMN_NORMALIZED_NAME_LEN, + N_COLUMN +}; + +typedef struct __GladeAdaptorChooserWidgetPrivate _GladeAdaptorChooserWidgetPrivate; +struct __GladeAdaptorChooserWidgetPrivate +{ + GtkTreeView *treeview; + GtkListStore *store; + GtkTreeModelFilter *treemodelfilter; + GtkSearchEntry *searchentry; + GtkEntryCompletion *entrycompletion; + GtkScrolledWindow *scrolledwindow; + + /* Needed for gtk_tree_view_column_set_cell_data_func() */ + GtkTreeViewColumn *column_icon; + GtkCellRenderer *icon_cell; + GtkTreeViewColumn *column_adaptor; + GtkCellRenderer *adaptor_cell; + + /* Properties */ + _GladeAdaptorChooserWidgetFlags flags; + GladeProject *project; + gboolean show_group_title; + gchar *search_text; +}; + +enum +{ + PROP_0, + + PROP_SHOW_FLAGS, + PROP_PROJECT, + PROP_SHOW_GROUP_TITLE +}; + +enum +{ + ADAPTOR_SELECTED, + + LAST_SIGNAL +}; + +static guint adaptor_chooser_signals[LAST_SIGNAL] = { 0 }; + +G_DEFINE_TYPE_WITH_PRIVATE (_GladeAdaptorChooserWidget, _glade_adaptor_chooser_widget, GTK_TYPE_BOX) + +#define GET_PRIVATE(d) ((_GladeAdaptorChooserWidgetPrivate *) _glade_adaptor_chooser_widget_get_instance_private((_GladeAdaptorChooserWidget*)d)) + +static void +_glade_adaptor_chooser_widget_init (_GladeAdaptorChooserWidget *chooser) +{ + gtk_widget_init_template (GTK_WIDGET (chooser)); +} + +static void +_glade_adaptor_chooser_widget_dispose (GObject *object) +{ + _glade_adaptor_chooser_widget_set_project (GLADE_ADAPTOR_CHOOSER_WIDGET (object), NULL); + + G_OBJECT_CLASS (_glade_adaptor_chooser_widget_parent_class)->dispose (object); +} + +static void +_glade_adaptor_chooser_widget_finalize (GObject *object) +{ + _GladeAdaptorChooserWidgetPrivate *priv = GET_PRIVATE (object); + + g_clear_pointer (&priv->search_text, g_free); + + G_OBJECT_CLASS (_glade_adaptor_chooser_widget_parent_class)->finalize (object); +} + +static void +_glade_adaptor_chooser_widget_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + _GladeAdaptorChooserWidgetPrivate *priv; + + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER_WIDGET (object)); + + priv = GET_PRIVATE (object); + + switch (prop_id) + { + case PROP_SHOW_FLAGS: + priv->flags = g_value_get_flags (value); + break; + case PROP_PROJECT: + _glade_adaptor_chooser_widget_set_project (GLADE_ADAPTOR_CHOOSER_WIDGET (object), + g_value_get_object (value)); + break; + case PROP_SHOW_GROUP_TITLE: + priv->show_group_title = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_glade_adaptor_chooser_widget_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + _GladeAdaptorChooserWidgetPrivate *priv; + + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER_WIDGET (object)); + + priv = GET_PRIVATE (object); + + switch (prop_id) + { + case PROP_SHOW_FLAGS: + g_value_set_flags (value, priv->flags); + break; + case PROP_PROJECT: + g_value_set_object (value, priv->project); + break; + case PROP_SHOW_GROUP_TITLE: + g_value_set_boolean (value, priv->show_group_title); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static inline gchar * +normalize_name (const gchar *name) +{ + gchar *normalized_name = g_utf8_normalize (name, -1, G_NORMALIZE_DEFAULT); + gchar *casefold_name = g_utf8_casefold (normalized_name, -1); + g_free (normalized_name); + return casefold_name; +} + +static inline void +store_append_adaptor (GtkListStore *store, GladeWidgetAdaptor *adaptor) +{ + gchar *normalized_name = normalize_name (glade_widget_adaptor_get_display_name (adaptor)); + + gtk_list_store_insert_with_values (store, NULL, -1, + COLUMN_ADAPTOR, adaptor, + COLUMN_NORMALIZED_NAME, normalized_name, + COLUMN_NORMALIZED_NAME_LEN, strlen (normalized_name), + -1); + g_free (normalized_name); +} + +static void +on_treeview_row_activated (GtkTreeView *tree_view, + GtkTreePath *path, + GtkTreeViewColumn *column, + _GladeAdaptorChooserWidget *chooser) +{ + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (model, &iter, path)) + { + GladeWidgetAdaptor *adaptor; + + gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); + + if (!adaptor) + return; + + /* Emit selected signal */ + g_signal_emit (chooser, adaptor_chooser_signals[ADAPTOR_SELECTED], 0, adaptor); + + g_object_unref (adaptor); + } +} + +static void +on_searchentry_search_changed (GtkEntry *entry, + _GladeAdaptorChooserWidget *chooser) +{ + _GladeAdaptorChooserWidgetPrivate *priv = GET_PRIVATE (chooser); + const gchar *text = gtk_entry_get_text (entry); + + g_clear_pointer (&priv->search_text, g_free); + + if (g_utf8_strlen (text, -1)) + priv->search_text = normalize_name (text); + + gtk_tree_model_filter_refilter (priv->treemodelfilter); +} + +static void +on_searchentry_activate (GtkEntry *entry, _GladeAdaptorChooserWidget *chooser) +{ + _GladeAdaptorChooserWidgetPrivate *priv = GET_PRIVATE (chooser); + const gchar *text = gtk_entry_get_text (entry); + GladeWidgetAdaptor *adaptor; + + /* try to find an adaptor by name */ + if (!(adaptor = glade_widget_adaptor_get_by_name (text))) + { + GtkTreeModel *model = GTK_TREE_MODEL (priv->treemodelfilter); + gchar *normalized_name = normalize_name (text); + GtkTreeIter iter; + gboolean valid; + gint count = 0; + + valid = gtk_tree_model_get_iter_first (model, &iter); + + /* we could not find it check if we can find it by normalized name */ + while (valid) + { + gchar *name; + + gtk_tree_model_get (model, &iter, COLUMN_NORMALIZED_NAME, &name, -1); + + if (g_strcmp0 (name, normalized_name) == 0) + { + gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); + g_free (name); + break; + } + + valid = gtk_tree_model_iter_next (model, &iter); + g_free (name); + count++; + } + + /* if not, and there is only one row, then we select that one */ + if (!adaptor && count == 1 && gtk_tree_model_get_iter_first (model, &iter)) + gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); + + g_free (normalized_name); + } + + if (adaptor) + g_signal_emit (chooser, adaptor_chooser_signals[ADAPTOR_SELECTED], 0, adaptor); +} + +static gboolean +chooser_match_func (_GladeAdaptorChooserWidget *chooser, + GtkTreeModel *model, + const gchar *key, + GtkTreeIter *iter) +{ + gboolean visible; + gint name_len; + gchar *name; + + if (!key || *key == '\0') + return TRUE; + + gtk_tree_model_get (model, iter, + COLUMN_NORMALIZED_NAME, &name, + COLUMN_NORMALIZED_NAME_LEN, &name_len, + -1); + if (!name) + return FALSE; + + visible = (g_strstr_len (name, name_len, key) != NULL); + g_free (name); + + return visible; +} + +static gboolean +treemodelfilter_visible_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer data) +{ + _GladeAdaptorChooserWidgetPrivate *priv = GET_PRIVATE (data); + GladeWidgetAdaptor *adaptor = NULL; + gboolean visible = TRUE; + + gtk_tree_model_get (model, iter, COLUMN_ADAPTOR, &adaptor, -1); + + if (!adaptor) + return priv->show_group_title && !priv->search_text; + + /* Skip classes not available in project target version */ + if (priv->project) + { + const gchar *catalog = NULL; + gint major, minor; + + catalog = glade_widget_adaptor_get_catalog (adaptor); + glade_project_get_target_version (priv->project, catalog, &major, &minor); + + visible = GLADE_WIDGET_ADAPTOR_VERSION_CHECK (adaptor, major, minor); + } + + if (visible && priv->flags) + { + GType type = glade_widget_adaptor_get_object_type (adaptor); + _GladeAdaptorChooserWidgetFlags flags = priv->flags; + + /* Skip adaptors according to flags */ + if (flags & GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_DEPRECATED && GLADE_WIDGET_ADAPTOR_DEPRECATED (adaptor)) + visible = FALSE; + else if (flags & GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_TOPLEVEL && GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor)) + visible = FALSE; + else if (flags & GLADE_ADAPTOR_CHOOSER_WIDGET_WIDGET && !g_type_is_a (type, GTK_TYPE_WIDGET)) + visible = FALSE; + else if (flags & GLADE_ADAPTOR_CHOOSER_WIDGET_TOPLEVEL && !GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor)) + visible = FALSE; + } + + if (visible && priv->search_text) + visible = chooser_match_func (data, model, priv->search_text, iter); + + g_clear_object (&adaptor); + + return visible; +} + +static gboolean +entrycompletion_match_func (GtkEntryCompletion *entry, const gchar *key, GtkTreeIter *iter, gpointer data) +{ + return chooser_match_func (data, gtk_entry_completion_get_model (entry), key, iter); +} + +static void +adaptor_icon_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + GladeWidgetAdaptor *adaptor; + gtk_tree_model_get (tree_model, iter, COLUMN_ADAPTOR, &adaptor, -1); + + if (adaptor) + g_object_set (cell, "sensitive", TRUE, "icon-name", glade_widget_adaptor_get_icon_name (adaptor), NULL); + else + g_object_set (cell, "sensitive", FALSE, "icon-name", "go-down-symbolic", NULL); + + g_clear_object (&adaptor); +} + +static void +adaptor_text_cell_data_func (GtkTreeViewColumn *tree_column, + GtkCellRenderer *cell, + GtkTreeModel *tree_model, + GtkTreeIter *iter, + gpointer data) +{ + GladeWidgetAdaptor *adaptor; + gchar *group; + + gtk_tree_model_get (tree_model, iter, + COLUMN_ADAPTOR, &adaptor, + COLUMN_GROUP, &group, + -1); + if (adaptor) + g_object_set (cell, + "sensitive", TRUE, + "text", glade_widget_adaptor_get_display_name (adaptor), + "style", PANGO_STYLE_NORMAL, + NULL); + else + g_object_set (cell, + "sensitive", FALSE, + "text", group, + "style", PANGO_STYLE_ITALIC, + NULL); + + g_clear_object (&adaptor); + g_free (group); +} + +static void +glade_adaptor_chooser_widget_drag_begin (GtkWidget *widget, + GdkDragContext *context, + gpointer data) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); + GtkTreeModel *model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + GladeWidgetAdaptor *adaptor; + gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); + _glade_dnd_set_icon_widget (context, + glade_widget_adaptor_get_icon_name (adaptor), + glade_widget_adaptor_get_display_name (adaptor)); + } +} + +static void +glade_adaptor_chooser_widget_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *data, + guint info, + guint time, + gpointer userdata) +{ + GtkTreeSelection *selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (widget)); + GtkTreeModel *model; + GtkTreeIter iter; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + GladeWidgetAdaptor *adaptor; + gtk_tree_model_get (model, &iter, COLUMN_ADAPTOR, &adaptor, -1); + _glade_dnd_set_data (data, G_OBJECT (adaptor)); + } +} + +static void +_glade_adaptor_chooser_widget_constructed (GObject *object) +{ + _GladeAdaptorChooserWidget *chooser = GLADE_ADAPTOR_CHOOSER_WIDGET (object); + _GladeAdaptorChooserWidgetPrivate *priv = GET_PRIVATE (chooser); + + /* Set cell data function: this save us from alocating name and icon name for each adaptor. */ + gtk_tree_view_column_set_cell_data_func (priv->column_icon, + priv->icon_cell, + adaptor_icon_cell_data_func, + NULL, NULL); + gtk_tree_view_column_set_cell_data_func (priv->column_adaptor, + priv->adaptor_cell, + adaptor_text_cell_data_func, + NULL, NULL); + /* Set tree model filter function */ + gtk_tree_model_filter_set_visible_func (priv->treemodelfilter, + treemodelfilter_visible_func, + chooser, NULL); + /* Set completion match function */ + gtk_entry_completion_set_match_func (priv->entrycompletion, + entrycompletion_match_func, + chooser, NULL); + /* Enable Drag & Drop */ + gtk_tree_view_enable_model_drag_source (priv->treeview, GDK_BUTTON1_MASK, + _glade_dnd_get_target (), 1, GDK_ACTION_MOVE | GDK_ACTION_COPY); + g_signal_connect_after (priv->treeview, "drag-begin", + G_CALLBACK (glade_adaptor_chooser_widget_drag_begin), + NULL); + g_signal_connect (priv->treeview, "drag-data-get", + G_CALLBACK (glade_adaptor_chooser_widget_drag_data_get), + NULL); +} + +static void +_glade_adaptor_chooser_widget_map (GtkWidget *widget) +{ + GtkWidget *toplevel = gtk_widget_get_toplevel (widget); + + if (toplevel) + { + _GladeAdaptorChooserWidgetPrivate *priv = GET_PRIVATE (widget); + gint height = gtk_widget_get_allocated_height (toplevel) - 100; + + if (height > 512) + height = height * 0.75; + + gtk_scrolled_window_set_max_content_height (priv->scrolledwindow, height); + } + + GTK_WIDGET_CLASS (_glade_adaptor_chooser_widget_parent_class)->map (widget); +} + +static GType +_glade_adaptor_chooser_widget_flags_get_type (void) +{ + static GType etype = 0; + if (G_UNLIKELY(etype == 0)) { + static const GFlagsValue values[] = { + { GLADE_ADAPTOR_CHOOSER_WIDGET_WIDGET, "GLADE_ADAPTOR_CHOOSER_WIDGET_WIDGET", "widget" }, + { GLADE_ADAPTOR_CHOOSER_WIDGET_TOPLEVEL, "GLADE_ADAPTOR_CHOOSER_WIDGET_TOPLEVEL", "toplevel" }, + { GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_TOPLEVEL, "GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_TOPLEVEL", "skip-toplevel" }, + { GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_DEPRECATED, "GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_DEPRECATED", "skip-deprecated" }, + { 0, NULL, NULL } + }; + etype = g_flags_register_static (g_intern_static_string ("_GladeAdaptorChooserWidgetFlag"), values); + } + return etype; +} + +static void +_glade_adaptor_chooser_widget_class_init (_GladeAdaptorChooserWidgetClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = _glade_adaptor_chooser_widget_dispose; + object_class->finalize = _glade_adaptor_chooser_widget_finalize; + object_class->set_property = _glade_adaptor_chooser_widget_set_property; + object_class->get_property = _glade_adaptor_chooser_widget_get_property; + object_class->constructed = _glade_adaptor_chooser_widget_constructed; + + widget_class->map = _glade_adaptor_chooser_widget_map; + + g_object_class_install_property (object_class, + PROP_SHOW_FLAGS, + g_param_spec_flags ("show-flags", + "Show flags", + "Widget adaptors show flags", + _glade_adaptor_chooser_widget_flags_get_type (), + 0, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_SHOW_GROUP_TITLE, + g_param_spec_boolean ("show-group-title", + "Show group title", + "Whether to show the group title", + FALSE, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + g_object_class_install_property (object_class, + PROP_PROJECT, + g_param_spec_object ("project", + "Glade Project", + "If set, use project target version to skip unsupported classes", + GLADE_TYPE_PROJECT, + G_PARAM_READWRITE)); + + adaptor_chooser_signals[ADAPTOR_SELECTED] = + g_signal_new ("adaptor-selected", G_OBJECT_CLASS_TYPE (klass), 0, 0, + NULL, NULL, NULL, + G_TYPE_NONE, 1, + GLADE_TYPE_WIDGET_ADAPTOR); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-adaptor-chooser-widget.ui"); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, treeview); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, store); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, treemodelfilter); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, searchentry); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, entrycompletion); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, column_icon); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, icon_cell); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, column_adaptor); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, adaptor_cell); + gtk_widget_class_bind_template_child_private (widget_class, _GladeAdaptorChooserWidget, scrolledwindow); + gtk_widget_class_bind_template_callback (widget_class, on_treeview_row_activated); + gtk_widget_class_bind_template_callback (widget_class, on_searchentry_search_changed); + gtk_widget_class_bind_template_callback (widget_class, on_searchentry_activate); +} + +GtkWidget * +_glade_adaptor_chooser_widget_new (_GladeAdaptorChooserWidgetFlags flags, GladeProject *project) +{ + return GTK_WIDGET (g_object_new (GLADE_TYPE_ADAPTOR_CHOOSER_WIDGET, + "show-flags", flags, + "project", project, + NULL)); +} + +void +_glade_adaptor_chooser_widget_set_project (_GladeAdaptorChooserWidget *chooser, + GladeProject *project) +{ + _GladeAdaptorChooserWidgetPrivate *priv; + + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER_WIDGET (chooser)); + priv = GET_PRIVATE (chooser); + + if (priv->project) + { + g_object_remove_weak_pointer (G_OBJECT (priv->project), (gpointer *) &priv->project); + priv->project = NULL; + } + + if (project) + { + priv->project = project; + g_object_add_weak_pointer (G_OBJECT (project), (gpointer *) &priv->project); + + gtk_tree_model_filter_refilter (priv->treemodelfilter); + } +} + +void +_glade_adaptor_chooser_widget_populate (_GladeAdaptorChooserWidget *chooser) +{ + GList *l; + + for (l = glade_app_get_catalogs (); l; l = g_list_next (l)) + _glade_adaptor_chooser_widget_add_catalog (chooser, l->data); +} + +void +_glade_adaptor_chooser_widget_add_catalog (_GladeAdaptorChooserWidget *chooser, + GladeCatalog *catalog) +{ + GList *groups; + + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER_WIDGET (chooser)); + + for (groups = glade_catalog_get_widget_groups (catalog); groups; + groups = g_list_next (groups)) + _glade_adaptor_chooser_widget_add_group (chooser, groups->data); +} + +void +_glade_adaptor_chooser_widget_add_group (_GladeAdaptorChooserWidget *chooser, + GladeWidgetGroup *group) +{ + _GladeAdaptorChooserWidgetPrivate *priv; + const GList *adaptors; + + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER_WIDGET (chooser)); + priv = GET_PRIVATE (chooser); + + if (priv->show_group_title) + gtk_list_store_insert_with_values (priv->store, NULL, -1, + COLUMN_GROUP, glade_widget_group_get_title (group), + -1); + + for (adaptors = glade_widget_group_get_adaptors (group); adaptors; + adaptors = g_list_next (adaptors)) + store_append_adaptor (priv->store, adaptors->data); +} + diff --git a/gladeui/glade-adaptor-chooser-widget.h b/gladeui/glade-adaptor-chooser-widget.h new file mode 100644 index 0000000..7a1d4cd --- /dev/null +++ b/gladeui/glade-adaptor-chooser-widget.h @@ -0,0 +1,64 @@ +/* + * glade-adaptor-chooser-widget.h + * + * Copyright (C) 2014-2017 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Juan Pablo Ugarte + */ + +#ifndef _GLADE_ADAPTOR_CHOOSER_WIDGET_H_ +#define _GLADE_ADAPTOR_CHOOSER_WIDGET_H_ + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_ADAPTOR_CHOOSER_WIDGET _glade_adaptor_chooser_widget_get_type () +G_DECLARE_DERIVABLE_TYPE (_GladeAdaptorChooserWidget, _glade_adaptor_chooser_widget, GLADE, ADAPTOR_CHOOSER_WIDGET, GtkBox) + +typedef enum +{ + GLADE_ADAPTOR_CHOOSER_WIDGET_WIDGET = 1 << 0, + GLADE_ADAPTOR_CHOOSER_WIDGET_TOPLEVEL = 1 << 1, + GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_TOPLEVEL = 1 << 2, + GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_DEPRECATED = 1 << 3 +} _GladeAdaptorChooserWidgetFlags; + +struct __GladeAdaptorChooserWidgetClass +{ + GtkBoxClass parent_class; +}; + +GtkWidget *_glade_adaptor_chooser_widget_new (_GladeAdaptorChooserWidgetFlags flags, + GladeProject *project); + +void _glade_adaptor_chooser_widget_set_project (_GladeAdaptorChooserWidget *chooser, + GladeProject *project); + +void _glade_adaptor_chooser_widget_populate (_GladeAdaptorChooserWidget *chooser); + +void _glade_adaptor_chooser_widget_add_catalog (_GladeAdaptorChooserWidget *chooser, + GladeCatalog *catalog); + +void _glade_adaptor_chooser_widget_add_group (_GladeAdaptorChooserWidget *chooser, + GladeWidgetGroup *group); + +G_END_DECLS + +#endif /* _GLADE_ADAPTOR_CHOOSER_WIDGET_H_ */ + diff --git a/gladeui/glade-adaptor-chooser-widget.ui b/gladeui/glade-adaptor-chooser-widget.ui new file mode 100644 index 0000000..3eb2ceb --- /dev/null +++ b/gladeui/glade-adaptor-chooser-widget.ui @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + store + + + treemodelfilter + 1 + True + False + False + + + diff --git a/gladeui/glade-adaptor-chooser.c b/gladeui/glade-adaptor-chooser.c new file mode 100644 index 0000000..f7b2b09 --- /dev/null +++ b/gladeui/glade-adaptor-chooser.c @@ -0,0 +1,436 @@ +/* + * glade-adaptor-chooser.c + * + * Copyright (C) 2017 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Juan Pablo Ugarte + */ + +#include "glade-adaptor-chooser-widget.h" +#include "glade-adaptor-chooser.h" +#include "glade-app.h" + +typedef struct +{ + GladeProject *project; + + GtkWidget *gtk_button_box; + GtkWidget *extra_button; + GtkWidget *others_button; + GtkImage *class_image; + GtkLabel *class_label; + GtkWidget *all_button; + + GtkWidget *others_chooser; + GtkWidget *all_chooser; + GList *choosers; + + gboolean needs_update; +} GladeAdaptorChooserPrivate; + +struct _GladeAdaptorChooser +{ + GtkBox parent_instance; +}; + +enum +{ + PROP_0, + PROP_PROJECT, + + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES]; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeAdaptorChooser, + glade_adaptor_chooser, + GTK_TYPE_BOX); + +#define GET_PRIVATE(d) ((GladeAdaptorChooserPrivate *) glade_adaptor_chooser_get_instance_private((GladeAdaptorChooser*)d)) + +static void +glade_adaptor_chooser_init (GladeAdaptorChooser *chooser) +{ + gtk_widget_init_template (GTK_WIDGET (chooser)); +} + +static void +glade_adaptor_chooser_finalize (GObject *object) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (object); + + g_list_free (priv->choosers); + + G_OBJECT_CLASS (glade_adaptor_chooser_parent_class)->finalize (object); +} + +static void +glade_adaptor_chooser_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER (object)); + + switch (prop_id) + { + case PROP_PROJECT: + glade_adaptor_chooser_set_project (GLADE_ADAPTOR_CHOOSER (object), + g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_adaptor_chooser_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeAdaptorChooserPrivate *priv; + + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER (object)); + priv = GET_PRIVATE (object); + + switch (prop_id) + { + case PROP_PROJECT: + g_value_set_object (value, priv->project); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_adaptor_selected (GtkWidget *widget, + GladeWidgetAdaptor *adaptor, + GladeAdaptorChooser *chooser) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + + /* Auto-create toplevel types */ + if (GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor)) + { + glade_command_create (adaptor, NULL, NULL, priv->project); + } + else + { + glade_project_set_add_item (priv->project, adaptor); + glade_project_set_pointer_mode (priv->project, GLADE_POINTER_ADD_WIDGET); + } + + gtk_popover_popdown (GTK_POPOVER (gtk_widget_get_parent (widget))); +} + +static void +glade_adaptor_chooser_button_add_chooser (GtkWidget *button, GtkWidget *chooser) +{ + GtkPopover *popover = gtk_menu_button_get_popover (GTK_MENU_BUTTON (button)); + + if (!popover) + { + popover = GTK_POPOVER (gtk_popover_new (button)); + gtk_menu_button_set_popover (GTK_MENU_BUTTON (button), + GTK_WIDGET (popover)); + } + + gtk_container_add (GTK_CONTAINER (popover), chooser); + gtk_widget_show (chooser); +} + +static GtkWidget * +glade_adaptor_chooser_add_chooser (GladeAdaptorChooser *chooser, + gboolean show_group_title) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + GtkWidget *chooser_widget = g_object_new (GLADE_TYPE_ADAPTOR_CHOOSER_WIDGET, + "show-group-title", show_group_title, + NULL); + + priv->choosers = g_list_prepend (priv->choosers, chooser_widget); + g_signal_connect (chooser_widget, "adaptor-selected", + G_CALLBACK (on_adaptor_selected), + chooser); + + return chooser_widget; +} + +static void +button_box_populate_from_catalog (GladeAdaptorChooser *chooser, + GladeCatalog *catalog) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + GtkWidget *extra_chooser = NULL; + GList *groups; + + groups = glade_catalog_get_widget_groups (catalog); + gtk_box_set_homogeneous (GTK_BOX (priv->gtk_button_box), FALSE); + + for (; groups; groups = g_list_next (groups)) + { + GladeWidgetGroup *group = GLADE_WIDGET_GROUP (groups->data); + + if (!glade_widget_group_get_adaptors (group)) + continue; + + if (glade_widget_group_get_expanded (group)) + { + GtkWidget *button, *chooser_widget; + + chooser_widget = glade_adaptor_chooser_add_chooser (chooser, FALSE); + button = gtk_menu_button_new (); + gtk_button_set_label (GTK_BUTTON (button), glade_widget_group_get_title (group)); + glade_adaptor_chooser_button_add_chooser (button, chooser_widget); + _glade_adaptor_chooser_widget_add_group (GLADE_ADAPTOR_CHOOSER_WIDGET (chooser_widget), group); + gtk_box_pack_start (GTK_BOX (priv->gtk_button_box), button, FALSE, FALSE, 0); + gtk_widget_show (button); + } + else + { + if (!extra_chooser) + { + extra_chooser = glade_adaptor_chooser_add_chooser (chooser, TRUE); + glade_adaptor_chooser_button_add_chooser (priv->extra_button, extra_chooser); + gtk_widget_show (priv->extra_button); + } + + _glade_adaptor_chooser_widget_add_group (GLADE_ADAPTOR_CHOOSER_WIDGET (extra_chooser), group); + } + } +} + +static void +remove_chooser_widget (GladeAdaptorChooser *chooser, GtkWidget *widget) +{ + if (widget) + { + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + priv->choosers = g_list_remove (priv->choosers, widget); + gtk_widget_destroy (widget); + } +} + +static void +update_all_others_chooser (GladeAdaptorChooser *chooser) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + GladeCatalog *gtk_catalog; + GList *l; + + priv->needs_update = FALSE; + + gtk_catalog = glade_app_get_catalog ("gtk+"); + + remove_chooser_widget (chooser, priv->others_chooser); + remove_chooser_widget (chooser, priv->all_chooser); + + priv->others_chooser = glade_adaptor_chooser_add_chooser (chooser, TRUE); + priv->all_chooser = glade_adaptor_chooser_add_chooser (chooser, TRUE); + + glade_adaptor_chooser_button_add_chooser (priv->others_button, priv->others_chooser); + glade_adaptor_chooser_button_add_chooser (priv->all_button, priv->all_chooser); + + /* then the rest */ + for (l = glade_app_get_catalogs (); l; l = g_list_next (l)) + { + GladeCatalog *catalog = l->data; + + _glade_adaptor_chooser_widget_add_catalog (GLADE_ADAPTOR_CHOOSER_WIDGET (priv->all_chooser), catalog); + + if (catalog != gtk_catalog) + _glade_adaptor_chooser_widget_add_catalog (GLADE_ADAPTOR_CHOOSER_WIDGET (priv->others_chooser), catalog); + } + + _glade_adaptor_chooser_widget_set_project (GLADE_ADAPTOR_CHOOSER_WIDGET (priv->others_chooser), priv->project); + _glade_adaptor_chooser_widget_set_project (GLADE_ADAPTOR_CHOOSER_WIDGET (priv->all_chooser), priv->project); +} + +static void +on_widget_adaptor_registered (GladeApp *app, + GladeWidgetAdaptor *adaptor, + GladeAdaptorChooser *chooser) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + priv->needs_update = TRUE; +} + +static void +on_button_clicked (GtkButton *button, GladeAdaptorChooser *chooser) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + + if (priv->needs_update) + update_all_others_chooser (chooser); +} + +static void +glade_adaptor_chooser_constructed (GObject *object) +{ + GladeAdaptorChooser *chooser = GLADE_ADAPTOR_CHOOSER (object); + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + GladeCatalog *gtk_catalog; + + /* GTK+ catalog goes first subdivided by group */ + gtk_catalog = glade_app_get_catalog ("gtk+"); + button_box_populate_from_catalog (chooser, gtk_catalog); + + update_all_others_chooser (chooser); + + g_signal_connect (glade_app_get(), "widget-adaptor-registered", + G_CALLBACK (on_widget_adaptor_registered), + chooser); + + g_signal_connect (priv->others_button, "clicked", + G_CALLBACK (on_button_clicked), + chooser); + g_signal_connect (priv->all_button, "clicked", + G_CALLBACK (on_button_clicked), + chooser); +} + +static void +glade_adaptor_chooser_class_init (GladeAdaptorChooserClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->finalize = glade_adaptor_chooser_finalize; + object_class->constructed = glade_adaptor_chooser_constructed; + object_class->set_property = glade_adaptor_chooser_set_property; + object_class->get_property = glade_adaptor_chooser_get_property; + + /* Properties */ + properties[PROP_PROJECT] = + g_param_spec_object ("project", "Project", + "This adaptor chooser's current project", + GLADE_TYPE_PROJECT, + G_PARAM_READWRITE); + + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-adaptor-chooser.ui"); + gtk_widget_class_bind_template_child_private (widget_class, GladeAdaptorChooser, gtk_button_box); + gtk_widget_class_bind_template_child_private (widget_class, GladeAdaptorChooser, extra_button); + gtk_widget_class_bind_template_child_private (widget_class, GladeAdaptorChooser, others_button); + gtk_widget_class_bind_template_child_private (widget_class, GladeAdaptorChooser, class_image); + gtk_widget_class_bind_template_child_private (widget_class, GladeAdaptorChooser, class_label); + gtk_widget_class_bind_template_child_private (widget_class, GladeAdaptorChooser, all_button); +} + +/* Public API */ + +/** + * glade_adaptor_chooser_new: + * + * Returns: (transfer full): A new #GladeAdaptorChooser + */ +GtkWidget * +glade_adaptor_chooser_new () +{ + return (GtkWidget*) g_object_new (GLADE_TYPE_ADAPTOR_CHOOSER, NULL); +} + +static void +glade_adaptor_chooser_update_adaptor (GladeAdaptorChooser *chooser) +{ + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + GladeWidgetAdaptor *adaptor; + + if (priv->project && (adaptor = glade_project_get_add_item (priv->project))) + { + gtk_image_set_from_icon_name (priv->class_image, + glade_widget_adaptor_get_icon_name (adaptor), + GTK_ICON_SIZE_BUTTON); + gtk_label_set_label (priv->class_label, + glade_widget_adaptor_get_display_name (adaptor)); + } + else + { + gtk_image_set_from_pixbuf (priv->class_image, NULL); + gtk_label_set_label (priv->class_label, ""); + } +} + +static void +on_project_weak_notify (gpointer data, GObject *project) +{ + GladeAdaptorChooser *chooser = data; + GladeAdaptorChooserPrivate *priv = GET_PRIVATE (chooser); + + priv->project = NULL; +} + +void +glade_adaptor_chooser_set_project (GladeAdaptorChooser *chooser, + GladeProject *project) +{ + GladeAdaptorChooserPrivate *priv; + GList *l; + + g_return_if_fail (GLADE_IS_ADAPTOR_CHOOSER (chooser)); + priv = GET_PRIVATE (chooser); + + if (priv->project) + { + g_signal_handlers_disconnect_by_func (G_OBJECT (priv->project), + G_CALLBACK (glade_adaptor_chooser_update_adaptor), + chooser); + g_object_weak_unref (G_OBJECT (priv->project), on_project_weak_notify, chooser); + priv->project = NULL; + } + + if (project) + { + priv->project = project; + g_object_weak_ref (G_OBJECT (project), on_project_weak_notify, chooser); + + g_signal_connect_swapped (G_OBJECT (project), "notify::add-item", + G_CALLBACK (glade_adaptor_chooser_update_adaptor), + chooser); + gtk_widget_set_sensitive (GTK_WIDGET (chooser), TRUE); + } + else + gtk_widget_set_sensitive (GTK_WIDGET (chooser), FALSE); + + /* Set project in chooser for filter to work */ + for (l = priv->choosers; l; l = g_list_next (l)) + _glade_adaptor_chooser_widget_set_project (l->data, project); + + /* Update class image and label */ + glade_adaptor_chooser_update_adaptor (chooser); +} + +/** + * glade_adaptor_chooser_get_project: + * @chooser: a #GladeAdaptorChooser + * + * Returns: (transfer full): A #GladeProject + */ +GladeProject * +glade_adaptor_chooser_get_project (GladeAdaptorChooser *chooser) +{ + g_return_val_if_fail (GLADE_IS_ADAPTOR_CHOOSER (chooser), NULL); + + return GET_PRIVATE (chooser)->project; +} diff --git a/gladeui/glade-adaptor-chooser.h b/gladeui/glade-adaptor-chooser.h new file mode 100644 index 0000000..226db8c --- /dev/null +++ b/gladeui/glade-adaptor-chooser.h @@ -0,0 +1,43 @@ +/* + * glade-adaptor-chooser.h + * + * Copyright (C) 2017 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Juan Pablo Ugarte + */ + +#ifndef _GLADE_ADAPTOR_CHOOSER_H_ +#define _GLADE_ADAPTOR_CHOOSER_H_ + +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_ADAPTOR_CHOOSER (glade_adaptor_chooser_get_type ()) +G_DECLARE_FINAL_TYPE (GladeAdaptorChooser, glade_adaptor_chooser, GLADE, ADAPTOR_CHOOSER, GtkBox) + +GtkWidget *glade_adaptor_chooser_new (void); + +void glade_adaptor_chooser_set_project (GladeAdaptorChooser *chooser, + GladeProject *project); +GladeProject *glade_adaptor_chooser_get_project (GladeAdaptorChooser *chooser); + +G_END_DECLS + +#endif /* _GLADE_ADAPTOR_CHOOSER_H_ */ diff --git a/gladeui/glade-adaptor-chooser.ui b/gladeui/glade-adaptor-chooser.ui new file mode 100644 index 0000000..8364c40 --- /dev/null +++ b/gladeui/glade-adaptor-chooser.ui @@ -0,0 +1,114 @@ + + + + + + diff --git a/gladeui/glade-app.c b/gladeui/glade-app.c new file mode 100644 index 0000000..2942eba --- /dev/null +++ b/gladeui/glade-app.c @@ -0,0 +1,970 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Naba Kumar + */ + +#include + +/** + * SECTION:glade-app + * @Short_Description: The central control point of the Glade core. + * + * This main control object is where we try to draw the line between + * what is the Glade core and what is the main application. The main + * application must derive from the GladeApp object and create an instance + * to initialize the Glade core. + */ + +#include "glade.h" +#include "glade-debug.h" +#include "glade-cursor.h" +#include "glade-catalog.h" +#include "glade-design-view.h" +#include "glade-design-layout.h" +#include "glade-marshallers.h" +#include "glade-accumulators.h" + +#include +#include +#include +#include +#include +#include + +#ifdef MAC_BUNDLE +# include +#endif + +#define GLADE_CONFIG_FILENAME "glade.conf" + +enum +{ + DOC_SEARCH, + SIGNAL_EDITOR_CREATED, + WIDGET_ADAPTOR_REGISTERED, + LAST_SIGNAL +}; + +typedef struct _GladeAppPrivate GladeAppPrivate; +struct _GladeAppPrivate +{ + GtkWidget *window; + + GladeClipboard *clipboard; /* See glade-clipboard */ + GList *catalogs; /* See glade-catalog */ + + GList *projects; /* The list of Projects */ + + GKeyFile *config; /* The configuration file */ + + GtkAccelGroup *accel_group; /* Default acceleration group for this app */ +}; + +static guint glade_app_signals[LAST_SIGNAL] = { 0 }; + +/* installation paths */ +static gchar *catalogs_dir = NULL; +static gchar *modules_dir = NULL; +static gchar *pixmaps_dir = NULL; +static gchar *locale_dir = NULL; +static gchar *bin_dir = NULL; +static gchar *lib_dir = NULL; + +static GladeApp *singleton_app = NULL; +static gboolean check_initialised = FALSE; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeApp, glade_app, G_TYPE_OBJECT); + +/***************************************************************** + * GObjectClass * + *****************************************************************/ +static GObject * +glade_app_constructor (GType type, + guint n_construct_properties, + GObjectConstructParam *construct_properties) +{ + GObject *object; + + /* singleton */ + if (!singleton_app) + { + object = G_OBJECT_CLASS (glade_app_parent_class)->constructor (type, + n_construct_properties, + construct_properties); + singleton_app = GLADE_APP (object); + } + else + { + g_object_ref (singleton_app); + } + + return G_OBJECT (singleton_app); +} + + + +static void +glade_app_dispose (GObject *app) +{ + GladeAppPrivate *priv = glade_app_get_instance_private ((GladeApp *) app); + + if (priv->clipboard) + { + g_object_unref (priv->clipboard); + priv->clipboard = NULL; + } + /* FIXME: Remove projects */ + + if (priv->config) + { + g_key_file_free (priv->config); + priv->config = NULL; + } + + G_OBJECT_CLASS (glade_app_parent_class)->dispose (app); +} + +static void +glade_app_finalize (GObject *app) +{ + g_free (catalogs_dir); + g_free (modules_dir); + g_free (pixmaps_dir); + g_free (locale_dir); + g_free (bin_dir); + g_free (lib_dir); + + singleton_app = NULL; + check_initialised = FALSE; + + G_OBJECT_CLASS (glade_app_parent_class)->finalize (app); +} + +/* build package paths at runtime */ +static void +build_package_paths (void) +{ + const gchar *path; + + path = g_getenv (GLADE_ENV_PIXMAP_DIR); + if (path) + pixmaps_dir = g_strdup (path); + +#if defined (G_OS_WIN32) || defined (MAC_BUNDLE) + gchar *prefix; + +# ifdef G_OS_WIN32 + prefix = g_win32_get_package_installation_directory_of_module (NULL); + +# else // defined (MAC_BUNDLE) + prefix = quartz_application_get_resource_path (); + +# endif + + if (!pixmaps_dir) + pixmaps_dir = g_build_filename (prefix, "share", PACKAGE_NAME, "pixmaps", NULL); + + catalogs_dir = g_build_filename (prefix, "share", PACKAGE_NAME, "catalogs", NULL); + modules_dir = g_build_filename (prefix, "lib", PACKAGE_NAME, "modules", NULL); + locale_dir = g_build_filename (prefix, "share", "locale", NULL); + bin_dir = g_build_filename (prefix, "bin", NULL); + lib_dir = g_build_filename (prefix, "lib", NULL); + + g_free (prefix); +#else + catalogs_dir = g_strdup (GLADE_CATALOGSDIR); + modules_dir = g_strdup (GLADE_MODULESDIR); + + if (!pixmaps_dir) + pixmaps_dir = g_strdup (GLADE_PIXMAPSDIR); + locale_dir = g_strdup (GLADE_LOCALEDIR); + bin_dir = g_strdup (GLADE_BINDIR); + lib_dir = g_strdup (GLADE_LIBDIR); +#endif +} + +/* initialization function for libgladeui (not GladeApp) */ +static void +glade_init_check (void) +{ + if (check_initialised) + return; + + glade_init_debug_flags (); + + /* Make sure path accessors work on osx */ + build_package_paths (); + + bindtextdomain (GETTEXT_PACKAGE, locale_dir); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + check_initialised = TRUE; +} + +/***************************************************************** + * GladeAppClass * + *****************************************************************/ +const gchar * +glade_app_get_catalogs_dir (void) +{ + glade_init_check (); + + return catalogs_dir; +} + +const gchar * +glade_app_get_modules_dir (void) +{ + glade_init_check (); + + return modules_dir; +} + +const gchar * +glade_app_get_pixmaps_dir (void) +{ + glade_init_check (); + + return pixmaps_dir; +} + +const gchar * +glade_app_get_locale_dir (void) +{ + glade_init_check (); + + return locale_dir; +} + +const gchar * +glade_app_get_bin_dir (void) +{ + glade_init_check (); + + return bin_dir; +} + +const gchar * +glade_app_get_lib_dir (void) +{ + glade_init_check (); + + return lib_dir; +} + +static void +pointer_mode_register_icon (const gchar *icon_name, + gint real_size, + GladePointerMode mode, + GtkIconSize size) +{ + GdkPixbuf *pixbuf; + + if ((pixbuf = glade_utils_pointer_mode_render_icon (mode, size))) + { + gtk_icon_theme_add_builtin_icon (icon_name, real_size, pixbuf); + g_object_unref (pixbuf); + } +} + +static void +register_icon (const gchar *new_icon_name, + gint size, + const gchar *icon_name, + const gchar *file_name) +{ + GtkIconTheme *icon_theme = gtk_icon_theme_get_default (); + GdkPixbuf *pixbuf; + GtkIconInfo *info; + + if ((info = gtk_icon_theme_lookup_icon (icon_theme, icon_name, size, 0))) + { + pixbuf = gtk_icon_info_load_icon (info, NULL); + } + else + { + gchar *path = g_build_filename (glade_app_get_pixmaps_dir (), file_name, NULL); + pixbuf = gdk_pixbuf_new_from_file (path, NULL); + g_free (path); + } + + if (pixbuf) + { + gtk_icon_theme_add_builtin_icon (new_icon_name, size, pixbuf); + g_object_unref (pixbuf); + } +} + +/* + * glade_app_register_icon_names: + * @size: icon size + * + * Register a new icon name for most of GladePointerMode. + * After calling this function "glade-selector", "glade-drag-resize", + * "glade-margin-edit" and "glade-align-edit" icon names will be available. + */ +static void +glade_app_register_icon_names (GtkIconSize size) +{ + gint w, h, real_size; + + if (gtk_icon_size_lookup (size, &w, &h) == FALSE) + return; + + real_size = MIN (w, h); + + pointer_mode_register_icon ("glade-selector", real_size, GLADE_POINTER_SELECT, size); + pointer_mode_register_icon ("glade-drag-resize", real_size, GLADE_POINTER_DRAG_RESIZE, size); + pointer_mode_register_icon ("glade-margin-edit", real_size, GLADE_POINTER_MARGIN_EDIT, size); + pointer_mode_register_icon ("glade-align-edit", real_size, GLADE_POINTER_ALIGN_EDIT, size); + + register_icon ("glade-devhelp", real_size, + GLADE_DEVHELP_ICON_NAME, + GLADE_DEVHELP_FALLBACK_ICON_FILE); +} + +/** + * glade_init: + * + * Initialization function for libgladeui (not #GladeApp) + * It builds paths, bind text domain, and register icons + */ +void +glade_init (void) +{ + static gboolean init = FALSE; + + if (init) return; + + glade_init_check (); + + /* Register icons needed by the UI */ + glade_app_register_icon_names (GTK_ICON_SIZE_LARGE_TOOLBAR); + + init = TRUE; +} + +static void +glade_app_init (GladeApp *app) +{ + static gboolean initialized = FALSE; + GladeAppPrivate *priv = glade_app_get_instance_private (app); + + singleton_app = app; + + glade_init (); + + if (!initialized) + { + GtkIconTheme *default_icon_theme = gtk_icon_theme_get_default (); + const gchar *path; + + gtk_icon_theme_append_search_path (default_icon_theme, pixmaps_dir); + + /* Handle extra icon theme paths. Needed for tests to work */ + if ((path = g_getenv (GLADE_ENV_ICON_THEME_PATH))) + { + gchar **tokens = g_strsplit (path, ":", -1); + gint i; + + for (i = 0; tokens[i]; i++) + gtk_icon_theme_append_search_path (default_icon_theme, tokens[i]); + + g_strfreev (tokens); + } + + glade_cursor_init (); + + initialized = TRUE; + } + + priv->accel_group = NULL; + + /* Initialize app objects */ + priv->catalogs = (GList *) glade_catalog_load_all (); + + /* Create clipboard */ + priv->clipboard = glade_clipboard_new (); + + /* Load the configuration file */ + priv->config = g_key_file_ref (glade_app_get_config ()); +} + +static void +glade_app_event_handler (GdkEvent *event, gpointer data) +{ + if (glade_app_do_event (event)) return; + + gtk_main_do_event (event); +} + +static void +glade_app_class_init (GladeAppClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->constructor = glade_app_constructor; + object_class->dispose = glade_app_dispose; + object_class->finalize = glade_app_finalize; + + /** + * GladeApp::doc-search: + * @gladeeditor: the #GladeEditor which received the signal. + * @arg1: the (#gchar *) book to search or %NULL + * @arg2: the (#gchar *) page to search or %NULL + * @arg3: the (#gchar *) search string or %NULL + * + * Emitted when the glade core requests that a doc-search be performed. + */ + glade_app_signals[DOC_SEARCH] = + g_signal_new ("doc-search", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, 0, NULL, NULL, + _glade_marshal_VOID__STRING_STRING_STRING, + G_TYPE_NONE, 3, + G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING); + + /** + * GladeApp::signal-editor-created: + * @gladeapp: the #GladeApp which received the signal. + * @signal_editor: the new #GladeSignalEditor. + * + * Emitted when a new signal editor created. + * A tree view is created in the default handler. + * Connect your handler before the default handler for setting a custom column or renderer + * and after it for connecting to the tree view signals + */ + glade_app_signals[SIGNAL_EDITOR_CREATED] = + g_signal_new ("signal-editor-created", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _glade_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + + /** + * GladeApp::widget-adaptor-registered: + * @gladeapp: the #GladeApp which received the signal. + * @adaptor: the newly registered #GladeWidgetAdaptor. + * + * Emitted when a new widget adaptor is registered. + */ + glade_app_signals[WIDGET_ADAPTOR_REGISTERED] = + g_signal_new ("widget-adaptor-registered", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _glade_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, G_TYPE_OBJECT); + + gdk_event_handler_set (glade_app_event_handler, NULL, NULL); +} + +/***************************************************************** + * Public API * + *****************************************************************/ + +/** + * glade_app_do_event: + * @event: the event to process. + * + * This function has to be called in an event handler for widget selection to work. + * See gdk_event_handler_set() + * + * Returns: true if the event was handled. + */ +gboolean +glade_app_do_event (GdkEvent *event) +{ + GdkWindow *window = event->any.window; + GtkWidget *layout; + gpointer widget; + + if (window == NULL) return FALSE; + + gdk_window_get_user_data (window, &widget); + + /* As a slight optimization we could replace gtk_widget_get_ancestor() + * with a custom function that only iterates trough parents with windows. + */ + if (widget && GLADE_WIDGET_IS_EVENT (event->type) && + (layout = gtk_widget_get_ancestor (widget, GLADE_TYPE_DESIGN_LAYOUT))) + return _glade_design_layout_do_event (GLADE_DESIGN_LAYOUT (layout), event); + + return FALSE; +} + +/** + * glade_app_config_save + * + * Saves the #GKeyFile to "g_get_user_config_dir()/GLADE_CONFIG_FILENAME" + * + * Returns: 0 on success. + */ +gint +glade_app_config_save () +{ + GIOChannel *channel; + GIOStatus status; + gchar *data = NULL, *filename; + const gchar *config_dir = g_get_user_config_dir (); + GError *error = NULL; + gsize size, written, bytes_written = 0; + static gboolean error_shown = FALSE; + GladeApp *app; + GladeAppPrivate *priv; + + /* If we had any errors; wait untill next session to retry. + */ + if (error_shown) + return -1; + + app = glade_app_get (); + priv = glade_app_get_instance_private (app); + + /* Just in case... try to create the config directory */ + if (g_file_test (config_dir, G_FILE_TEST_IS_DIR) == FALSE) + { + if (g_file_test (config_dir, G_FILE_TEST_EXISTS)) + { + /* Config dir exists but is not a directory */ + glade_util_ui_message + (glade_app_get_window (), + GLADE_UI_ERROR, NULL, + _("Trying to save private data to %s directory " + "but it is a regular file.\n" + "No private data will be saved in this session"), config_dir); + error_shown = TRUE; + return -1; + } + else if (g_mkdir (config_dir, S_IRWXU) != 0) + { + /* Doesnt exist; failed to create */ + glade_util_ui_message + (glade_app_get_window (), + GLADE_UI_ERROR, NULL, + _("Failed to create directory %s to save private data.\n" + "No private data will be saved in this session"), config_dir); + error_shown = TRUE; + return -1; + } + } + + filename = g_build_filename (config_dir, GLADE_CONFIG_FILENAME, NULL); + + if ((channel = g_io_channel_new_file (filename, "w", &error)) != NULL) + { + if ((data = + g_key_file_to_data (priv->config, &size, &error)) != NULL) + { + + /* Implement loop here */ + while ((status = g_io_channel_write_chars (channel, data + bytes_written, /* Offset of write */ + size - bytes_written, /* Size left to write */ + &written, + &error)) != + G_IO_STATUS_ERROR && (bytes_written + written) < size) + bytes_written += written; + + if (status == G_IO_STATUS_ERROR) + { + glade_util_ui_message + (glade_app_get_window (), + GLADE_UI_ERROR, NULL, + _("Error writing private data to %s (%s).\n" + "No private data will be saved in this session"), + filename, error->message); + error_shown = TRUE; + } + g_free (data); + } + else + { + glade_util_ui_message + (glade_app_get_window (), + GLADE_UI_ERROR, NULL, + _("Error serializing configuration data to save (%s).\n" + "No private data will be saved in this session"), + error->message); + error_shown = TRUE; + } + g_io_channel_shutdown (channel, TRUE, NULL); + g_io_channel_unref (channel); + } + else + { + glade_util_ui_message + (glade_app_get_window (), + GLADE_UI_ERROR, NULL, + _("Error opening %s to write private data (%s).\n" + "No private data will be saved in this session"), + filename, error->message); + error_shown = TRUE; + } + g_free (filename); + + if (error) + { + g_error_free (error); + return -1; + } + return 0; +} + +/** + * glade_app_get: + * + * Returns: (transfer none): the #GladeApp + */ +GladeApp * +glade_app_get (void) +{ + if (!singleton_app) + { + singleton_app = glade_app_new (); + } + + return singleton_app; +} + +/** + * glade_app_set_window: + * @window: (transfer full): a #GtkWidget + * + * Set the window of the application + */ +void +glade_app_set_window (GtkWidget *window) +{ + GladeApp *app = glade_app_get (); + GladeAppPrivate *priv = glade_app_get_instance_private (app); + + priv->window = window; +} + +/** + * glade_app_get_catalog: + * @name: the name of the catalog + * + * Returns: (transfer none) (nullable): a #GladeCatalog or %NULL if none is found + */ +GladeCatalog * +glade_app_get_catalog (const gchar *name) +{ + GladeApp *app = glade_app_get (); + GladeAppPrivate *priv = glade_app_get_instance_private (app); + GList *list; + GladeCatalog *catalog; + + g_return_val_if_fail (name && name[0], NULL); + + for (list = priv->catalogs; list; list = list->next) + { + catalog = list->data; + if (!strcmp (glade_catalog_get_name (catalog), name)) + return catalog; + } + return NULL; +} + +/** + * glade_app_get_catalog_version: + * @name: the name of the #GladeCatalog + * @major: (out) (optional): the major version + * @minor: (out) (optional): the minor version + * + * Returns: %TRUE if the catalog has been found. It is a programming error + * to call this function with an unexisting catalog, returns %FALSE in this + * case and throws a warning. + */ +gboolean +glade_app_get_catalog_version (const gchar *name, gint *major, gint *minor) +{ + GladeCatalog *catalog = glade_app_get_catalog (name); + + g_return_val_if_fail (catalog != NULL, FALSE); + + if (major) + *major = glade_catalog_get_major_version (catalog); + if (minor) + *minor = glade_catalog_get_minor_version (catalog); + + return TRUE; +} + +/** + * glade_app_get_catalogs: + * + * Returns: (transfer none) (element-type GladeCatalog): a list of #GladeCatalog + */ +GList * +glade_app_get_catalogs (void) +{ + GladeApp *app = glade_app_get (); + GladeAppPrivate *priv = glade_app_get_instance_private (app); + + return priv->catalogs; +} + +/** + * glade_app_get_window: + * + * Returns: (transfer none): a #GtkWidget + */ +GtkWidget * +glade_app_get_window (void) +{ + GladeApp *app = glade_app_get (); + GladeAppPrivate *priv = glade_app_get_instance_private (app); + + return priv->window; +} + +/** + * glade_app_get_clipboard: + * + * Returns: (transfer none): a #GladeClipboard + */ +GladeClipboard * +glade_app_get_clipboard (void) +{ + GladeApp *app = glade_app_get (); + GladeAppPrivate *priv = glade_app_get_instance_private (app); + + return priv->clipboard; +} + +/** + * glade_app_get_projects: + * + * Returns: (element-type GladeCatalog) (transfer none): a list of #GladeCatalog + */ +GList * +glade_app_get_projects (void) +{ + GladeApp *app = glade_app_get (); + GladeAppPrivate *priv = glade_app_get_instance_private (app); + + return priv->projects; +} + +/** + * glade_app_get_config: + * + * Returns: (transfer full): a #GKeyFile + */ +GKeyFile * +glade_app_get_config (void) +{ + static GKeyFile *config = NULL; + + if (config == NULL) + { + gchar *filename = g_build_filename (g_get_user_config_dir (), + GLADE_CONFIG_FILENAME, NULL); + config = g_key_file_new (); + g_key_file_load_from_file (config, filename, G_KEY_FILE_NONE, NULL); + g_free (filename); + } + + return config; +} + +gboolean +glade_app_is_project_loaded (const gchar *project_path) +{ + GladeApp *app; + GladeAppPrivate *priv; + GList *list; + gboolean loaded = FALSE; + + if (project_path == NULL) + return FALSE; + + app = glade_app_get (); + priv = glade_app_get_instance_private (app); + + for (list = priv->projects; list; list = list->next) + { + GladeProject *cur_project = GLADE_PROJECT (list->data); + + if ((loaded = glade_project_get_path (cur_project) && + (strcmp (glade_project_get_path (cur_project), project_path) == 0))) + break; + } + + return loaded; +} + +/** + * glade_app_get_project_by_path: + * @project_path: The path of an open project + * + * Finds an open project with @path + * + * Returns: (nullable) (transfer none): A #GladeProject, or NULL if no such open project was found + */ +GladeProject * +glade_app_get_project_by_path (const gchar *project_path) +{ + GladeApp *app; + GladeAppPrivate *priv; + GList *l; + gchar *canonical_path; + + if (project_path == NULL) + return NULL; + + app = glade_app_get (); + priv = glade_app_get_instance_private (app); + + canonical_path = glade_util_canonical_path (project_path); + + for (l = priv->projects; l; l = l->next) + { + GladeProject *project = (GladeProject *) l->data; + + if (glade_project_get_path (project) && + strcmp (canonical_path, glade_project_get_path (project)) == 0) + { + g_free (canonical_path); + return project; + } + } + + g_free (canonical_path); + + return NULL; +} + +/** + * glade_app_add_project: + * @project: the project to add to the #GladeApp + */ +void +glade_app_add_project (GladeProject *project) +{ + GladeApp *app; + GladeAppPrivate *priv; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + app = glade_app_get (); + priv = glade_app_get_instance_private (app); + + /* If the project was previously loaded, don't re-load */ + if (g_list_find (priv->projects, project) != NULL) + return; + + /* Take a reference for GladeApp here... */ + priv->projects = g_list_append (priv->projects, g_object_ref (project)); +} + +/** + * glade_app_remove_project: + * @project: the project to remove from the #GladeApp + */ +void +glade_app_remove_project (GladeProject *project) +{ + GladeApp *app; + GladeAppPrivate *priv; + g_return_if_fail (GLADE_IS_PROJECT (project)); + + app = glade_app_get (); + priv = glade_app_get_instance_private (app); + + priv->projects = g_list_remove (priv->projects, project); + + /* Its safe to just release the project as the project emits a + * "close" signal and everyone is responsable for cleaning up at + * that point. + */ + g_object_unref (project); +} + +/** + * glade_app_set_accel_group: + * @accel_group: (transfer full): a #GtkAccelGroup to set + * + * Sets @accel_group to app. + * The acceleration group will made available for editor dialog windows + * from the plugin backend. + */ +void +glade_app_set_accel_group (GtkAccelGroup *accel_group) +{ + GladeApp *app; + GladeAppPrivate *priv; + + g_return_if_fail (GTK_IS_ACCEL_GROUP (accel_group)); + + app = glade_app_get (); + priv = glade_app_get_instance_private (app); + + priv->accel_group = accel_group; +} + +/** + * glade_app_get_accel_group: + * + * Returns: (transfer none): the #GtkAccelGroup + */ +GtkAccelGroup * +glade_app_get_accel_group (void) +{ + GladeApp *app = glade_app_get (); + GladeAppPrivate *priv = glade_app_get_instance_private (app); + + return priv->accel_group; +} + +/** + * glade_app_new: + * + * Returns: (transfer full): the #GladeApp + */ +GladeApp * +glade_app_new (void) +{ + return g_object_new (GLADE_TYPE_APP, NULL); +} + +/** + * glade_app_search_docs: + * @book: the name of a book + * @page: the name of a page + * @search: the search query + * + * Searches for @book, @page and @search in the documentation. + */ +void +glade_app_search_docs (const gchar *book, + const gchar *page, + const gchar *search) +{ + GladeApp *app; + + app = glade_app_get (); + + g_signal_emit (G_OBJECT (app), glade_app_signals[DOC_SEARCH], 0, + book, page, search); +} diff --git a/gladeui/glade-app.h b/gladeui/glade-app.h new file mode 100644 index 0000000..d5c9c27 --- /dev/null +++ b/gladeui/glade-app.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Naba Kumar + */ + +#ifndef __GLADE_APP_H__ +#define __GLADE_APP_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_APP glade_app_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeApp, glade_app, GLADE, APP, GObject) + +#define GLADE_ENV_CATALOG_PATH "GLADE_CATALOG_SEARCH_PATH" +#define GLADE_ENV_MODULE_PATH "GLADE_MODULE_SEARCH_PATH" +#define GLADE_ENV_TESTING "GLADE_TESTING" +#define GLADE_ENV_PIXMAP_DIR "GLADE_PIXMAP_DIR" +#define GLADE_ENV_ICON_THEME_PATH "GLADE_ICON_THEME_PATH" +#define GLADE_ENV_BUNDLED "GLADE_BUNDLED" + +struct _GladeAppClass +{ + GObjectClass parent_class; + + gpointer padding[6]; +}; + +void glade_init (void); + +GladeApp* glade_app_new (void); +GladeApp* glade_app_get (void); +GKeyFile* glade_app_get_config (void); +gint glade_app_config_save (void); + +gboolean glade_app_do_event (GdkEvent *event); + +gboolean glade_app_get_catalog_version (const gchar *name, + gint *major, + gint *minor); +GList *glade_app_get_catalogs (void); +GladeCatalog *glade_app_get_catalog (const gchar *name); +GladeClipboard* glade_app_get_clipboard (void); + +void glade_app_add_project (GladeProject *project); +void glade_app_remove_project (GladeProject *project); +GList* glade_app_get_projects (void); +gboolean glade_app_is_project_loaded (const gchar *project_path); +GladeProject* glade_app_get_project_by_path (const gchar *project_path); + +void glade_app_set_window (GtkWidget *window); +GtkWidget* glade_app_get_window (void); + +void glade_app_set_accel_group (GtkAccelGroup *accel_group); +GtkAccelGroup *glade_app_get_accel_group (void); + +void glade_app_search_docs (const gchar *book, + const gchar *page, + const gchar *search); + +/* package paths */ +const gchar *glade_app_get_catalogs_dir (void) G_GNUC_CONST; +const gchar *glade_app_get_modules_dir (void) G_GNUC_CONST; +const gchar *glade_app_get_pixmaps_dir (void) G_GNUC_CONST; +const gchar *glade_app_get_locale_dir (void) G_GNUC_CONST; +const gchar *glade_app_get_bin_dir (void) G_GNUC_CONST; +const gchar *glade_app_get_lib_dir (void) G_GNUC_CONST; + +G_END_DECLS + +#endif /* __GLADE_APP_H__ */ diff --git a/gladeui/glade-base-editor.c b/gladeui/glade-base-editor.c new file mode 100644 index 0000000..31e764f --- /dev/null +++ b/gladeui/glade-base-editor.c @@ -0,0 +1,2174 @@ + +/* + * Copyright (C) 2006-2016 Juan Pablo Ugarte. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Juan Pablo Ugarte + */ + +#include "config.h" + +/** + * SECTION:glade-base-editor + * @Short_Description: A customisable editor + * + * Convenience object to edit containers where placeholders do not make sense, like GtkMenubar. + */ + +#include "glade.h" +#include "glade-marshallers.h" +#include "glade-editor-property.h" +#include "glade-base-editor.h" +#include "glade-app.h" +#include "glade-popup.h" +#include "glade-accumulators.h" + +#include +#include +#include + +typedef enum +{ + GLADE_BASE_EDITOR_GTYPE, + GLADE_BASE_EDITOR_CLASS_NAME, + GLADE_BASE_EDITOR_TYPES_N_COLUMNS +} GladeBaseEditorChildEnum; + +typedef enum +{ + GLADE_BASE_EDITOR_GWIDGET, + GLADE_BASE_EDITOR_OBJECT, + GLADE_BASE_EDITOR_TYPE_NAME, + GLADE_BASE_EDITOR_NAME, + GLADE_BASE_EDITOR_CHILD_TYPES, + GLADE_BASE_EDITOR_N_COLUMNS +} GladeBaseEditorEnum; + +typedef enum { + ADD_ROOT = 0, + ADD_SIBLING, + ADD_CHILD +} GladeBaseEditorAddMode; + +typedef struct +{ + GType parent_type; + GtkTreeModel *children; +} ChildTypeTab; + +typedef struct _GladeBaseEditorPrivate +{ + GladeWidget *gcontainer; /* The container we are editing */ + + /* Editor UI */ + GtkWidget *paned, *table, *treeview, *tip_label; + GtkWidget *add_button, *delete_button, *help_button; + GladeSignalEditor *signal_editor; + + GList *child_types; + + GtkTreeModel *model; + GladeProject *project; + + /* Add button data */ + GType add_type; + + /* Temporal variables */ + GtkTreeIter iter; /* used in idle functions */ + gint row; + + gboolean updating_treeview; + + guint properties_idle; +} GladeBaseEditorPrivate; + +enum +{ + SIGNAL_CHILD_SELECTED, + SIGNAL_CHANGE_TYPE, + SIGNAL_GET_DISPLAY_NAME, + SIGNAL_BUILD_CHILD, + SIGNAL_DELETE_CHILD, + SIGNAL_MOVE_CHILD, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CONTAINER, + N_PROPERTIES +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeBaseEditor, glade_base_editor, GTK_TYPE_BOX) + +static GParamSpec *properties[N_PROPERTIES]; +static guint glade_base_editor_signals[LAST_SIGNAL] = { 0 }; + +static void glade_base_editor_set_container (GladeBaseEditor *editor, + GObject *container); +static void glade_base_editor_block_callbacks (GladeBaseEditor *editor, + gboolean block); + + +static void +reset_child_types (GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GList *l; + ChildTypeTab *tab; + + for (l = priv->child_types; l; l = l->next) + { + tab = l->data; + g_object_unref (tab->children); + g_free (tab); + } + g_list_free (priv->child_types); + priv->child_types = NULL; +} + + +static gint +sort_type_by_hierarchy (ChildTypeTab *a, ChildTypeTab *b) +{ + if (g_type_is_a (a->parent_type, b->parent_type)) + return -1; + + return 1; +} + +static GtkTreeModel * +get_children_model_for_type (GladeBaseEditor *editor, GType type) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + GList *l; + for (l = priv->child_types; l; l = l->next) + { + ChildTypeTab *tab = l->data; + if (g_type_is_a (type, tab->parent_type)) + return tab->children; + } + return NULL; +} + +static GtkTreeModel * +get_children_model_for_child_type (GladeBaseEditor *editor, GType type) +{ + GList *l; + GtkTreeModel *model = NULL; + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + /* Get deep derived classes first and work up the sorted heirarchy */ + for (l = g_list_last (priv->child_types); model == NULL && l; + l = l->prev) + { + ChildTypeTab *tab = l->data; + GtkTreeIter iter; + GType iter_type; + + if (!gtk_tree_model_get_iter_first (tab->children, &iter)) + continue; + + do + { + gtk_tree_model_get (tab->children, &iter, + GLADE_BASE_EDITOR_GTYPE, &iter_type, -1); + + /* Find exact match types in this case */ + if (iter_type == type) + model = tab->children; + + } + while (model == NULL && gtk_tree_model_iter_next (tab->children, &iter)); + } + + return model; +} + +static gboolean +glade_base_editor_get_type_info (GladeBaseEditor *e, + GtkTreeIter *retiter, + GType child_type, + ...) +{ + GtkTreeModel *model; + GtkTreeIter iter; + GType type; + + model = get_children_model_for_child_type (e, child_type); + + if (!model || gtk_tree_model_get_iter_first (model, &iter) == FALSE) + return FALSE; + + do + { + gtk_tree_model_get (model, &iter, GLADE_BASE_EDITOR_GTYPE, &type, -1); + + if (child_type == type) + { + va_list args; + va_start (args, child_type); + gtk_tree_model_get_valist (model, &iter, args); + va_end (args); + if (retiter) + *retiter = iter; + return TRUE; + } + } + while (gtk_tree_model_iter_next (model, &iter)); + + return FALSE; +} + +static gchar * +glade_base_editor_get_display_name (GladeBaseEditor *editor, + GladeWidget *gchild) +{ + gchar *retval; + g_signal_emit (editor, + glade_base_editor_signals[SIGNAL_GET_DISPLAY_NAME], + 0, gchild, &retval); + + return retval; +} + +static void +glade_base_editor_fill_store_real (GladeBaseEditor *e, + GladeWidget *gwidget, + GtkTreeIter *parent) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GList *children, *l; + GtkTreeIter iter; + + children = glade_widget_get_children (gwidget); + + for (l = children; l; l = l->next) + { + GladeWidget *gchild; + GObject *child = l->data; + gchar *type_name = NULL, *name; + + gchild = glade_widget_get_from_gobject (child); + + /* Have to check parents here for compatibility (could be the parenting menuitem of this menu + * supports a menuitem...) */ + if (glade_base_editor_get_type_info (e, NULL, + G_OBJECT_TYPE (child), + GLADE_BASE_EDITOR_CLASS_NAME, + &type_name, -1)) + { + gtk_tree_store_append (GTK_TREE_STORE (priv->model), &iter, parent); + + name = glade_base_editor_get_display_name (e, gchild); + + gtk_tree_store_set (GTK_TREE_STORE (priv->model), &iter, + GLADE_BASE_EDITOR_GWIDGET, gchild, + GLADE_BASE_EDITOR_OBJECT, child, + GLADE_BASE_EDITOR_TYPE_NAME, type_name, + GLADE_BASE_EDITOR_NAME, name, + GLADE_BASE_EDITOR_CHILD_TYPES, + get_children_model_for_child_type (e, G_OBJECT_TYPE (child)), + -1); + + glade_base_editor_fill_store_real (e, gchild, &iter); + + g_free (name); + g_free (type_name); + } + else + glade_base_editor_fill_store_real (e, gchild, parent); + } + + g_list_free (children); +} + +static void +glade_base_editor_fill_store (GladeBaseEditor *e) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + + gtk_tree_store_clear (GTK_TREE_STORE (priv->model)); + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), NULL); + glade_base_editor_fill_store_real (e, priv->gcontainer, NULL); + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), priv->model); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->treeview)); + +} + +static gboolean +glade_base_editor_get_child_selected (GladeBaseEditor *e, GtkTreeIter *iter) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GtkTreeSelection *sel = + gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->treeview)); + return (sel) ? gtk_tree_selection_get_selected (sel, NULL, iter) : FALSE; +} + +/* Forward declaration for glade_base_editor_project_widget_name_changed */ +static void +glade_base_editor_project_widget_name_changed (GladeProject *project, + GladeWidget *widget, + GladeBaseEditor *editor); + + +static GladeWidget * +glade_base_editor_delegate_build_child (GladeBaseEditor *editor, + GladeWidget *parent, + GType type) +{ + GladeWidget *child = NULL; + g_signal_emit (editor, glade_base_editor_signals[SIGNAL_BUILD_CHILD], + 0, parent, type, &child); + return child; +} + +static gboolean +glade_base_editor_delegate_delete_child (GladeBaseEditor *editor, + GladeWidget *parent, + GladeWidget *child) +{ + gboolean retval; + + g_signal_emit (editor, glade_base_editor_signals[SIGNAL_DELETE_CHILD], + 0, parent, child, &retval); + + return retval; +} + +static void +glade_base_editor_name_activate (GtkEntry *entry, GladeWidget *gchild) +{ + const gchar *text = gtk_entry_get_text (GTK_ENTRY (entry)); + GladeBaseEditor *editor = g_object_get_data (G_OBJECT (entry), "editor"); + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + gchar *new_name = NULL; + + if (text == NULL || text[0] == '\0') + { + /* If we are explicitly trying to set the widget name to be empty, + * then we must not allow it there are any active references to + * the widget which would otherwise break. + */ + if (!glade_widget_has_prop_refs (gchild)) + new_name = glade_project_new_widget_name (priv->project, NULL, GLADE_UNNAMED_PREFIX); + } + else + new_name = g_strdup (text); + + if (new_name && new_name[0]) + { + g_signal_handlers_block_by_func (priv->project, + glade_base_editor_project_widget_name_changed, editor); + glade_command_set_name (gchild, new_name); + g_signal_handlers_unblock_by_func (priv->project, + glade_base_editor_project_widget_name_changed, editor); + } + + g_free (new_name); +} + +static void +glade_base_editor_table_attach (GladeBaseEditor *e, + GtkWidget *child1, + GtkWidget *child2) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GtkGrid *table = GTK_GRID (priv->table); + gint row = priv->row; + + if (child1) + { + gtk_grid_attach (table, child1, 0, row, 1, 1); + gtk_widget_set_hexpand (child1, TRUE); + gtk_widget_show (child1); + } + + if (child2) + { + gtk_grid_attach (table, child2, 1, row, 1, 1); + gtk_widget_show (child2); + } + + priv->row++; +} + +static void +destroy_widget (GtkWidget *widget, gpointer data) +{ + gtk_widget_destroy (widget); +} + +static void +glade_base_editor_clear (GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + gtk_widget_show (priv->tip_label); + gtk_container_foreach (GTK_CONTAINER (priv->table), destroy_widget, NULL); + priv->row = 0; + gtk_widget_set_sensitive (priv->delete_button, FALSE); + glade_signal_editor_load_widget (priv->signal_editor, NULL); +} + +static void +glade_base_editor_treeview_cursor_changed (GtkTreeView *treeview, + GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkTreeIter iter; + GObject *child; + GladeWidget *gchild; + + g_return_if_fail (GTK_IS_TREE_VIEW (treeview)); + + if (!glade_base_editor_get_child_selected (editor, &iter)) + return; + + glade_base_editor_clear (editor); + gtk_widget_set_sensitive (priv->delete_button, TRUE); + + gtk_tree_model_get (priv->model, &iter, + GLADE_BASE_EDITOR_GWIDGET, &gchild, + GLADE_BASE_EDITOR_OBJECT, &child, -1); + + g_object_unref (gchild); + g_object_unref (child); + + /* Emit child-selected signal and let the user add the properties */ + g_signal_emit (editor, glade_base_editor_signals[SIGNAL_CHILD_SELECTED], + 0, gchild); + + /* Update Signal Editor */ + glade_signal_editor_load_widget (priv->signal_editor, gchild); +} + +static gboolean +glade_base_editor_update_properties_idle (gpointer data) +{ + GladeBaseEditor *editor = (GladeBaseEditor *) data; + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + glade_base_editor_treeview_cursor_changed + (GTK_TREE_VIEW (priv->treeview), editor); + priv->properties_idle = 0; + return FALSE; +} + +/* XXX Can we make it crisper by removing this idle ?? */ +static void +glade_base_editor_update_properties (GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + g_return_if_fail (GLADE_IS_BASE_EDITOR (editor)); + + if (!priv->properties_idle) + priv->properties_idle = + g_idle_add (glade_base_editor_update_properties_idle, editor); +} + +static void +glade_base_editor_set_cursor (GladeBaseEditor *e, GtkTreeIter *iter) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GtkTreePath *path; + GtkTreeIter real_iter; + + if (iter == NULL && glade_base_editor_get_child_selected (e, &real_iter)) + iter = &real_iter; + + if (iter && (path = gtk_tree_model_get_path (priv->model, iter))) + { + gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->treeview), path, NULL, + FALSE); + gtk_tree_path_free (path); + } +} + +static gboolean +glade_base_editor_find_child_real (GladeBaseEditor *e, + GladeWidget *gchild, + GtkTreeIter *iter) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GtkTreeModel *model = priv->model; + GtkTreeIter child_iter; + GladeWidget *child; + + do + { + gtk_tree_model_get (model, iter, GLADE_BASE_EDITOR_GWIDGET, &child, -1); + g_object_unref (child); + + if (child == gchild) + return TRUE; + + if (gtk_tree_model_iter_children (model, &child_iter, iter)) + if (glade_base_editor_find_child_real (e, gchild, &child_iter)) + { + *iter = child_iter; + return TRUE; + } + } + while (gtk_tree_model_iter_next (model, iter)); + + return FALSE; +} + +static gboolean +glade_base_editor_find_child (GladeBaseEditor *e, + GladeWidget *child, + GtkTreeIter *iter) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + + if (gtk_tree_model_get_iter_first (priv->model, iter)) + return glade_base_editor_find_child_real (e, child, iter); + + return FALSE; +} + +static void +glade_base_editor_select_child (GladeBaseEditor *e, GladeWidget *child) +{ + GtkTreeIter iter; + + if (glade_base_editor_find_child (e, child, &iter)) + glade_base_editor_set_cursor (e, &iter); +} + +static void +glade_base_editor_child_change_type (GladeBaseEditor *editor, + GtkTreeIter *iter, + GType type) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GladeWidget *gchild; + GObject *child; + gchar *class_name; + gboolean retval; + + glade_base_editor_block_callbacks (editor, TRUE); + + /* Get old widget data */ + gtk_tree_model_get (priv->model, iter, + GLADE_BASE_EDITOR_GWIDGET, &gchild, + GLADE_BASE_EDITOR_OBJECT, &child, -1); + + g_object_unref (gchild); + g_object_unref (child); + + if (type == G_OBJECT_TYPE (child) || + !gchild || !glade_widget_get_parent (gchild)) + { + glade_base_editor_block_callbacks (editor, FALSE); + return; + } + + /* Start of glade-command */ + if (glade_base_editor_get_type_info (editor, NULL, + type, + GLADE_BASE_EDITOR_CLASS_NAME, + &class_name, -1)) + { + glade_command_push_group (_("Setting object type on %s to %s"), + glade_widget_get_name (gchild), class_name); + g_free (class_name); + } + else + { + glade_base_editor_block_callbacks (editor, FALSE); + return; + } + + g_signal_emit (editor, + glade_base_editor_signals[SIGNAL_CHANGE_TYPE], + 0, gchild, type, &retval); + + /* End of glade-command */ + glade_command_pop_group (); + + /* Update properties */ + glade_base_editor_update_properties (editor); + + glade_base_editor_block_callbacks (editor, FALSE); +} + +static void +glade_base_editor_type_changed (GtkComboBox *widget, GladeBaseEditor *e) +{ + GtkTreeIter iter, combo_iter; + GType type; + + if (!glade_base_editor_get_child_selected (e, &iter)) + return; + + gtk_combo_box_get_active_iter (widget, &combo_iter); + + gtk_tree_model_get (gtk_combo_box_get_model (widget), &combo_iter, + GLADE_BASE_EDITOR_GTYPE, &type, -1); + + glade_base_editor_child_change_type (e, &iter, type); +} + +static void +glade_base_editor_child_type_edited (GtkCellRendererText *cell, + const gchar *path_string, + const gchar *new_text, + GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkTreeModel *child_class; + GtkTreePath *path; + GtkTreeIter iter, combo_iter; + GType type; + gchar *type_name = NULL; + + path = gtk_tree_path_new_from_string (path_string); + gtk_tree_model_get_iter (priv->model, &iter, path); + gtk_tree_path_free (path); + + gtk_tree_model_get (priv->model, &iter, + GLADE_BASE_EDITOR_TYPE_NAME, &type_name, + GLADE_BASE_EDITOR_CHILD_TYPES, &child_class, -1); + + if (g_strcmp0 (type_name, new_text) == 0) + { + g_free (type_name); + g_object_unref (child_class); + return; + } + + /* Lookup GType */ + if (!gtk_tree_model_get_iter_first (child_class, &combo_iter)) + { + g_free (type_name); + g_object_unref (child_class); + return; + } + + g_free (type_name); + + do + { + gtk_tree_model_get (child_class, &combo_iter, + GLADE_BASE_EDITOR_GTYPE, &type, + GLADE_BASE_EDITOR_CLASS_NAME, &type_name, -1); + + if (strcmp (type_name, new_text) == 0) + { + g_free (type_name); + break; + } + + g_free (type_name); + } + while (gtk_tree_model_iter_next (child_class, &combo_iter)); + + glade_base_editor_child_change_type (editor, &iter, type); +} + +static void +glade_base_editor_reorder_children (GladeBaseEditor *editor, + GtkTreeIter *child) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkTreeModel *model = priv->model; + GladeWidget *gchild; + GladeProperty *property; + GtkTreeIter parent, iter; + gint position = 0; + + if (gtk_tree_model_iter_parent (model, &parent, child)) + gtk_tree_model_iter_children (model, &iter, &parent); + else + gtk_tree_model_get_iter_first (model, &iter); + + do + { + gtk_tree_model_get (model, &iter, GLADE_BASE_EDITOR_GWIDGET, &gchild, -1); + g_object_unref (gchild); + + if ((property = glade_widget_get_property (gchild, "position")) != NULL) + glade_command_set_property (property, position); + position++; + } + while (gtk_tree_model_iter_next (model, &iter)); +} + +static void +glade_base_editor_add_child (GladeBaseEditor *editor, + GType type, + GladeBaseEditorAddMode add_mode) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkTreeIter iter, new_iter; + GladeWidget *gparent, *gchild_new; + gchar *name, *class_name; + gboolean selected_iter = FALSE; + + glade_base_editor_block_callbacks (editor, TRUE); + + gparent = priv->gcontainer; + + if (add_mode != ADD_ROOT && + (selected_iter = glade_base_editor_get_child_selected (editor, &iter))) + { + if (add_mode == ADD_CHILD) + { + gtk_tree_model_get (priv->model, &iter, + GLADE_BASE_EDITOR_GWIDGET, &gparent, -1); + g_object_unref (gparent); + } + else if (add_mode == ADD_SIBLING && + gtk_tree_model_iter_parent (priv->model, &new_iter, &iter)) + { + gtk_tree_model_get (priv->model, &new_iter, + GLADE_BASE_EDITOR_GWIDGET, &gparent, -1); + g_object_unref (gparent); + } + } + + if (!glade_base_editor_get_type_info (editor, NULL, type, + GLADE_BASE_EDITOR_CLASS_NAME, + &class_name, -1)) + return; + + glade_command_push_group (_("Add a %s to %s"), class_name, + glade_widget_get_name (gparent)); + + /* Build Child */ + gchild_new = glade_base_editor_delegate_build_child (editor, gparent, type); + + if (gchild_new == NULL) + { + glade_command_pop_group (); + return; + } + + if (selected_iter) + { + if (add_mode == ADD_CHILD) + gtk_tree_store_append (GTK_TREE_STORE (priv->model), &new_iter, + &iter); + else + gtk_tree_store_insert_after (GTK_TREE_STORE (priv->model), + &new_iter, NULL, &iter); + } + else + gtk_tree_store_append (GTK_TREE_STORE (priv->model), &new_iter, + NULL); + + name = glade_base_editor_get_display_name (editor, gchild_new); + + gtk_tree_store_set (GTK_TREE_STORE (priv->model), &new_iter, + GLADE_BASE_EDITOR_GWIDGET, gchild_new, + GLADE_BASE_EDITOR_OBJECT, + glade_widget_get_object (gchild_new), + GLADE_BASE_EDITOR_TYPE_NAME, class_name, + GLADE_BASE_EDITOR_NAME, name, + GLADE_BASE_EDITOR_CHILD_TYPES, + get_children_model_for_type (editor, + G_OBJECT_TYPE (glade_widget_get_object (gparent))), + -1); + + glade_base_editor_reorder_children (editor, &new_iter); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->treeview)); + glade_base_editor_set_cursor (editor, &new_iter); + + glade_command_pop_group (); + + glade_base_editor_block_callbacks (editor, FALSE); + + g_free (name); + g_free (class_name); +} + + +static void +glade_base_editor_add_item_activate (GtkMenuItem *menuitem, + GladeBaseEditor *e) +{ + GObject *item = G_OBJECT (menuitem); + GType type = GPOINTER_TO_SIZE (g_object_get_data (item, "object_type")); + GladeBaseEditorAddMode add_mode = + GPOINTER_TO_INT (g_object_get_data (item, "object_add_mode")); + + glade_base_editor_add_child (e, type, add_mode); +} + +static GtkWidget * +glade_base_editor_popup (GladeBaseEditor *editor, GladeWidget *widget) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkWidget *popup, *item; + GtkTreeModel *model; + GtkTreeIter iter; + GType iter_type; + gchar *label; + gchar *class_name; + + if ((model = + get_children_model_for_child_type (editor, + G_OBJECT_TYPE (glade_widget_get_object (widget)))) == NULL) + model = + get_children_model_for_type (editor, + G_OBJECT_TYPE (glade_widget_get_object (priv->gcontainer))); + + g_assert (model); + + popup = gtk_menu_new (); + + if (gtk_tree_model_get_iter_first (model, &iter)) + do + { + gtk_tree_model_get (model, &iter, + GLADE_BASE_EDITOR_GTYPE, &iter_type, + GLADE_BASE_EDITOR_CLASS_NAME, &class_name, -1); + + label = g_strdup_printf (_("Add %s"), class_name); + + item = gtk_menu_item_new_with_label (label); + gtk_widget_show (item); + + g_object_set_data (G_OBJECT (item), "object_type", + GSIZE_TO_POINTER (iter_type)); + + g_object_set_data (G_OBJECT (item), "object_add_mode", + GINT_TO_POINTER (ADD_SIBLING)); + + g_signal_connect (item, "activate", + G_CALLBACK (glade_base_editor_add_item_activate), + editor); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), item); + + g_free (label); + g_free (class_name); + + } + while (gtk_tree_model_iter_next (model, &iter)); + + + if ((model = + get_children_model_for_type (editor, G_OBJECT_TYPE (glade_widget_get_object (widget)))) && + gtk_tree_model_get_iter_first (model, &iter)) + do + { + gtk_tree_model_get (model, &iter, + GLADE_BASE_EDITOR_GTYPE, &iter_type, + GLADE_BASE_EDITOR_CLASS_NAME, &class_name, -1); + + label = g_strdup_printf (_("Add child %s"), class_name); + + item = gtk_menu_item_new_with_label (label); + gtk_widget_show (item); + + g_object_set_data (G_OBJECT (item), "object_type", + GSIZE_TO_POINTER (iter_type)); + + g_object_set_data (G_OBJECT (item), "object_add_mode", + GINT_TO_POINTER (ADD_CHILD)); + + g_signal_connect (item, "activate", + G_CALLBACK (glade_base_editor_add_item_activate), + editor); + gtk_menu_shell_append (GTK_MENU_SHELL (popup), item); + + g_free (label); + g_free (class_name); + + } + while (gtk_tree_model_iter_next (model, &iter)); + + return popup; +} + +static gint +glade_base_editor_popup_handler (GtkWidget *treeview, + GdkEventButton *event, + GladeBaseEditor *e) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GtkTreePath *path; + GtkWidget *popup; + + if (glade_popup_is_popup_event (event)) + { + if (gtk_tree_view_get_path_at_pos (GTK_TREE_VIEW (treeview), + (gint) event->x, (gint) event->y, + &path, NULL, NULL, NULL)) + { + GtkTreeIter iter; + GladeWidget *gwidget; + + gtk_tree_view_set_cursor (GTK_TREE_VIEW (treeview), path, NULL, + FALSE); + + gtk_tree_model_get_iter (priv->model, &iter, path); + gtk_tree_model_get (priv->model, &iter, + GLADE_BASE_EDITOR_GWIDGET, &gwidget, -1); + + + popup = glade_base_editor_popup (e, gwidget); + + gtk_tree_path_free (path); + + gtk_menu_popup_at_pointer (GTK_MENU (popup), (GdkEvent*) event); + } + return TRUE; + } + + return FALSE; +} + + +static void +glade_base_editor_add_activate (GtkButton *button, GladeBaseEditor *e) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + + if (priv->add_type) + glade_base_editor_add_child (e, priv->add_type, ADD_ROOT); +} + +static void +glade_base_editor_delete_child (GladeBaseEditor *e) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GladeWidget *child, *gparent; + GtkTreeIter iter, parent; + + if (!glade_base_editor_get_child_selected (e, &iter)) + return; + + gtk_tree_model_get (priv->model, &iter, + GLADE_BASE_EDITOR_GWIDGET, &child, -1); + + if (gtk_tree_model_iter_parent (priv->model, &parent, &iter)) + gtk_tree_model_get (priv->model, &parent, + GLADE_BASE_EDITOR_GWIDGET, &gparent, -1); + else + gparent = priv->gcontainer; + + glade_command_push_group (_("Delete %s child from %s"), + glade_widget_get_name (child), + glade_widget_get_name (gparent)); + + /* Emit delete-child signal */ + glade_base_editor_delegate_delete_child (e, gparent, child); + + glade_command_pop_group (); +} + + +static gboolean +glade_base_editor_treeview_key_press_event (GtkWidget *widget, + GdkEventKey *event, + GladeBaseEditor *e) +{ + if (event->keyval == GDK_KEY_Delete) + glade_base_editor_delete_child (e); + + return FALSE; +} + +static void +glade_base_editor_delete_activate (GtkButton *button, GladeBaseEditor *e) +{ + glade_base_editor_delete_child (e); +} + +static gboolean +glade_base_editor_is_child (GladeBaseEditor *e, + GladeWidget *gchild, + gboolean valid_type) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GladeWidget *gcontainer = glade_widget_get_parent (gchild); + + if (!gcontainer) + return FALSE; + + if (valid_type) + { + GObject *child = glade_widget_get_object (gchild); + + if (glade_widget_get_internal (gchild) || + glade_base_editor_get_type_info (e, NULL, + G_OBJECT_TYPE (child), -1) == FALSE) + return FALSE; + + gcontainer = priv->gcontainer; + } + else + { + GtkTreeIter iter; + if (glade_base_editor_get_child_selected (e, &iter)) + gtk_tree_model_get (priv->model, &iter, + GLADE_BASE_EDITOR_GWIDGET, &gcontainer, -1); + else + return FALSE; + } + + while ((gchild = glade_widget_get_parent (gchild))) + if (gchild == gcontainer) + return TRUE; + + return FALSE; +} + +static gboolean +glade_base_editor_update_treeview_idle (gpointer data) +{ + GladeBaseEditor *e = data; + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + GList *selection = glade_project_selection_get (priv->project); + + glade_base_editor_block_callbacks (e, TRUE); + + glade_base_editor_fill_store (e); + glade_base_editor_clear (e); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->treeview)); + + if (selection) + { + GladeWidget *widget = + glade_widget_get_from_gobject (G_OBJECT (selection->data)); + if (glade_base_editor_is_child (e, widget, TRUE)) + glade_base_editor_select_child (e, widget); + } + + priv->updating_treeview = FALSE; + glade_base_editor_block_callbacks (e, FALSE); + + return FALSE; +} + +static void +glade_base_editor_project_widget_name_changed (GladeProject *project, + GladeWidget *widget, + GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GladeWidget *selected_child; + GtkTreeIter iter; + + if (glade_base_editor_get_child_selected (editor, &iter)) + { + gtk_tree_model_get (priv->model, &iter, + GLADE_BASE_EDITOR_GWIDGET, &selected_child, -1); + if (widget == selected_child) + glade_base_editor_update_properties (editor); + + g_object_unref (G_OBJECT (selected_child)); + } +} + +static void +glade_base_editor_project_closed (GladeProject *project, GladeBaseEditor *e) +{ + glade_base_editor_set_container (e, NULL); +} + +static void +glade_base_editor_reorder (GladeBaseEditor *editor, GtkTreeIter *iter) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GladeWidget *gchild, *gparent; + GtkTreeIter parent_iter; + gboolean retval; + + glade_command_push_group (_("Reorder %s's children"), + glade_widget_get_name (priv->gcontainer)); + + gtk_tree_model_get (priv->model, iter, GLADE_BASE_EDITOR_GWIDGET, &gchild, -1); + g_object_unref (G_OBJECT (gchild)); + + if (gtk_tree_model_iter_parent (priv->model, &parent_iter, iter)) + { + gtk_tree_model_get (priv->model, &parent_iter, + GLADE_BASE_EDITOR_GWIDGET, &gparent, -1); + g_object_unref (G_OBJECT (gparent)); + } + else + gparent = priv->gcontainer; + + g_signal_emit (editor, glade_base_editor_signals[SIGNAL_MOVE_CHILD], + 0, gparent, gchild, &retval); + + if (retval) + glade_base_editor_reorder_children (editor, iter); + else + { + glade_base_editor_clear (editor); + glade_base_editor_fill_store (editor); + glade_base_editor_find_child (editor, gchild, &priv->iter); + } + + glade_command_pop_group (); +} + +static gboolean +glade_base_editor_drag_and_drop_idle (gpointer data) +{ + GladeBaseEditor *e = data; + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + + glade_base_editor_reorder (e, &priv->iter); + gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->treeview)); + glade_base_editor_set_cursor (e, &priv->iter); + glade_base_editor_block_callbacks (e, FALSE); + + return FALSE; +} + +static void +glade_base_editor_row_inserted (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GladeBaseEditor *e) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + priv->iter = *iter; + glade_base_editor_block_callbacks (e, TRUE); + g_idle_add (glade_base_editor_drag_and_drop_idle, e); +} + +static void +glade_base_editor_project_remove_widget (GladeProject *project, + GladeWidget *widget, + GladeBaseEditor *e) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + if (widget == priv->gcontainer) + { + glade_base_editor_set_container (e, NULL); + return; + } + + if (glade_base_editor_is_child (e, widget, TRUE)) + { + GtkTreeIter iter; + if (glade_base_editor_find_child (e, widget, &iter)) + { + gtk_tree_store_remove (GTK_TREE_STORE (priv->model), &iter); + glade_base_editor_clear (e); + } + } + + if (glade_widget_get_internal (widget) && + glade_base_editor_is_child (e, widget, FALSE)) + glade_base_editor_update_properties (e); +} + +static void +glade_base_editor_project_add_widget (GladeProject *project, + GladeWidget *widget, + GladeBaseEditor *e) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (e); + if (priv->updating_treeview) + return; + + if (glade_base_editor_is_child (e, widget, TRUE)) + { + priv->updating_treeview = TRUE; + g_idle_add (glade_base_editor_update_treeview_idle, e); + } + + if (glade_widget_get_internal (widget) && + glade_base_editor_is_child (e, widget, FALSE)) + glade_base_editor_update_properties (e); +} + +static gboolean +glade_base_editor_update_display_name (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GladeBaseEditor *editor = data; + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GladeWidget *gchild; + gchar *name; + + gtk_tree_model_get (model, iter, GLADE_BASE_EDITOR_GWIDGET, &gchild, -1); + + name = glade_base_editor_get_display_name (editor, gchild); + + gtk_tree_store_set (GTK_TREE_STORE (priv->model), iter, + GLADE_BASE_EDITOR_NAME, name, -1); + g_free (name); + g_object_unref (G_OBJECT (gchild)); + + return FALSE; +} + +static void +glade_base_editor_project_changed (GladeProject *project, + GladeCommand *command, + gboolean forward, + GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + gtk_tree_model_foreach (priv->model, + glade_base_editor_update_display_name, editor); +} + + + +static void +glade_base_editor_project_disconnect (GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + if (priv->project == NULL) + return; + + g_signal_handlers_disconnect_by_func (priv->project, + glade_base_editor_project_closed, + editor); + + g_signal_handlers_disconnect_by_func (priv->project, + glade_base_editor_project_remove_widget, + editor); + + g_signal_handlers_disconnect_by_func (priv->project, + glade_base_editor_project_add_widget, + editor); + + g_signal_handlers_disconnect_by_func (priv->project, + glade_base_editor_project_widget_name_changed, + editor); + + g_signal_handlers_disconnect_by_func (priv->project, + glade_base_editor_project_changed, + editor); + + + if (priv->properties_idle) + g_source_remove (priv->properties_idle); + priv->properties_idle = 0; +} + +static void +glade_base_editor_set_container (GladeBaseEditor *editor, GObject *container) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + glade_base_editor_project_disconnect (editor); + + if (container == NULL) + { + reset_child_types (editor); + + priv->gcontainer = NULL; + priv->project = NULL; + glade_base_editor_block_callbacks (editor, TRUE); + glade_base_editor_clear (editor); + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), NULL); + gtk_tree_store_clear (GTK_TREE_STORE (priv->model)); + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), + priv->model); + + gtk_widget_set_sensitive (priv->paned, FALSE); + glade_base_editor_block_callbacks (editor, FALSE); + + glade_signal_editor_load_widget (priv->signal_editor, NULL); + + g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_CONTAINER]); + return; + } + + gtk_widget_set_sensitive (priv->paned, TRUE); + + priv->gcontainer = glade_widget_get_from_gobject (container); + + priv->project = glade_widget_get_project (priv->gcontainer); + + g_signal_connect (priv->project, "close", + G_CALLBACK (glade_base_editor_project_closed), editor); + + g_signal_connect (priv->project, "remove-widget", + G_CALLBACK (glade_base_editor_project_remove_widget), + editor); + + g_signal_connect (priv->project, "add-widget", + G_CALLBACK (glade_base_editor_project_add_widget), editor); + + g_signal_connect (priv->project, "widget-name-changed", + G_CALLBACK (glade_base_editor_project_widget_name_changed), + editor); + + g_signal_connect (priv->project, "changed", + G_CALLBACK (glade_base_editor_project_changed), editor); + + g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_CONTAINER]); +} + +/*************************** GladeBaseEditor Class ****************************/ +static void +glade_base_editor_dispose (GObject *object) +{ + GladeBaseEditor *editor = GLADE_BASE_EDITOR (object); + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + reset_child_types (editor); + + glade_base_editor_project_disconnect (editor); + priv->project = NULL; + + G_OBJECT_CLASS (glade_base_editor_parent_class)->dispose (object); +} + +static void +glade_base_editor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeBaseEditor *editor = GLADE_BASE_EDITOR (object); + + switch (prop_id) + { + case PROP_CONTAINER: + glade_base_editor_set_container (editor, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_base_editor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeBaseEditor *editor = GLADE_BASE_EDITOR (object); + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + switch (prop_id) + { + case PROP_CONTAINER: + g_value_set_object (value, glade_widget_get_object (priv->gcontainer)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + + +/* Default handlers */ +static gboolean +glade_base_editor_change_type (GladeBaseEditor *editor, + GladeWidget *gchild, + GType type) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GladeWidget *parent, *gchild_new; + GList *children, *l; + GObject *child_new; + GtkTreeIter iter; + gchar *name, *class_name; + + parent = glade_widget_get_parent (gchild); + + if (glade_base_editor_get_type_info (editor, NULL, type, + GLADE_BASE_EDITOR_CLASS_NAME, + &class_name, -1) == FALSE) + return TRUE; + + name = g_strdup (glade_widget_get_name (gchild)); + glade_base_editor_find_child (editor, gchild, &iter); + + /* Delete old widget first, we cant assume the old and new widget can live in + * the same parent simultaniously */ + glade_base_editor_delegate_delete_child (editor, parent, gchild); + + /* Create new widget */ + gchild_new = glade_base_editor_delegate_build_child (editor, parent, type); + + child_new = glade_widget_get_object (gchild_new); + + /* Cut and Paste children */ + if ((children = glade_widget_get_children (gchild)) != NULL) + { + GList *gchildren = NULL; + + l = children; + while (l) + { + GladeWidget *w = glade_widget_get_from_gobject (l->data); + + if (w && !glade_widget_get_internal (w)) + gchildren = g_list_prepend (gchildren, w); + + l = g_list_next (l); + } + + if (gchildren) + { + glade_command_dnd (gchildren, gchild_new, NULL); + + g_list_free (children); + g_list_free (gchildren); + } + } + + /* Copy properties */ + glade_widget_copy_properties (gchild_new, gchild, TRUE, TRUE); + + /* Apply packing properties to the new object + * + * No need to use GladeCommand here on the newly created widget, + * they just become the initial state for this object. + */ + l = glade_widget_get_packing_properties (gchild); + while (l) + { + GladeProperty *orig_prop = (GladeProperty *) l->data; + GladePropertyDef *pdef = glade_property_get_def (orig_prop); + GladeProperty *dup_prop = glade_widget_get_property (gchild_new, glade_property_def_id (pdef)); + glade_property_set_value (dup_prop, glade_property_inline_value (orig_prop)); + l = g_list_next (l); + } + + /* Set the name */ + glade_command_set_name (gchild_new, name); + + if (GTK_IS_WIDGET (child_new)) + gtk_widget_show_all (GTK_WIDGET (child_new)); + + /* XXX We should update the widget name in the visible tree here too */ + gtk_tree_store_set (GTK_TREE_STORE (priv->model), &iter, + GLADE_BASE_EDITOR_GWIDGET, gchild_new, + GLADE_BASE_EDITOR_OBJECT, child_new, + GLADE_BASE_EDITOR_TYPE_NAME, class_name, -1); + g_free (class_name); + g_free (name); + + return TRUE; +} + +static gchar * +glade_base_editor_get_display_name_impl (GladeBaseEditor *editor, + GladeWidget *gchild) +{ + return g_strdup (glade_widget_get_display_name (gchild)); +} + +static GladeWidget * +glade_base_editor_build_child (GladeBaseEditor *editor, + GladeWidget *gparent, + GType type) +{ + return glade_command_create (glade_widget_adaptor_get_by_type (type), + gparent, NULL, + glade_widget_get_project (gparent)); +} + +static gboolean +glade_base_editor_move_child (GladeBaseEditor *editor, + GladeWidget *gparent, + GladeWidget *gchild) +{ + GList list = { 0, }; + + if (gparent != glade_widget_get_parent (gchild)) + { + list.data = gchild; + glade_command_dnd (&list, gparent, NULL); + } + + return TRUE; +} + +static gboolean +glade_base_editor_delete_child_impl (GladeBaseEditor *editor, + GladeWidget *gparent, + GladeWidget *gchild) +{ + GList list = { 0, }; + + list.data = gchild; + glade_command_delete (&list); + + return TRUE; +} + +static void +glade_base_editor_block_callbacks (GladeBaseEditor *editor, gboolean block) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + if (block) + { + g_signal_handlers_block_by_func (priv->model, glade_base_editor_row_inserted, + editor); + if (priv->project) + { + g_signal_handlers_block_by_func (priv->project, + glade_base_editor_project_remove_widget, + editor); + g_signal_handlers_block_by_func (priv->project, + glade_base_editor_project_add_widget, + editor); + g_signal_handlers_block_by_func (priv->project, + glade_base_editor_project_changed, + editor); + } + } + else + { + g_signal_handlers_unblock_by_func (priv->model, + glade_base_editor_row_inserted, + editor); + if (priv->project) + { + g_signal_handlers_unblock_by_func (priv->project, + glade_base_editor_project_remove_widget, + editor); + g_signal_handlers_unblock_by_func (priv->project, + glade_base_editor_project_add_widget, + editor); + g_signal_handlers_unblock_by_func (priv->project, + glade_base_editor_project_changed, + editor); + } + } +} + +static void +glade_base_editor_realize_callback (GtkWidget *widget, gpointer user_data) +{ + GladeBaseEditor *editor = GLADE_BASE_EDITOR (widget); + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + glade_base_editor_block_callbacks (editor, TRUE); + + glade_base_editor_fill_store (editor); + gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->treeview)); + + glade_base_editor_block_callbacks (editor, FALSE); +} + +static void +glade_base_editor_init (GladeBaseEditor *editor) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + gtk_widget_init_template (GTK_WIDGET (editor)); + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (_("Label"), renderer, + "text", + GLADE_BASE_EDITOR_NAME, + NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (priv->treeview), column); + + renderer = gtk_cell_renderer_combo_new (); + g_object_set (renderer, + "has-entry", FALSE, + "text-column", GLADE_BASE_EDITOR_CLASS_NAME, + "editable", TRUE, NULL); + + g_signal_connect (G_OBJECT (renderer), "edited", + G_CALLBACK (glade_base_editor_child_type_edited), editor); + + column = gtk_tree_view_column_new_with_attributes (_("Type"), renderer, + "text", + GLADE_BASE_EDITOR_TYPE_NAME, + "model", + GLADE_BASE_EDITOR_CHILD_TYPES, + NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (priv->treeview), column); +} + +static void +glade_base_editor_class_init (GladeBaseEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = glade_base_editor_dispose; + object_class->set_property = glade_base_editor_set_property; + object_class->get_property = glade_base_editor_get_property; + + klass->change_type = glade_base_editor_change_type; + klass->get_display_name = glade_base_editor_get_display_name_impl; + klass->build_child = glade_base_editor_build_child; + klass->delete_child = glade_base_editor_delete_child_impl; + klass->move_child = glade_base_editor_move_child; + + properties[PROP_CONTAINER] = + g_param_spec_object ("container", + _("Container"), + _("The container object this editor is currently editing"), + G_TYPE_OBJECT, + G_PARAM_READWRITE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + /** + * GladeBaseEditor::child-selected: + * @gladebaseeditor: the #GladeBaseEditor which received the signal. + * @gchild: the selected #GladeWidget. + * + * Emitted when the user selects a child in the editor's treeview. + * You can add the relevant child properties here using + * glade_base_editor_add_default_properties() and glade_base_editor_add_properties() + * You can also add labels with glade_base_editor_add_label to make the + * editor look pretty. + */ + glade_base_editor_signals[SIGNAL_CHILD_SELECTED] = + g_signal_new ("child-selected", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeBaseEditorClass, child_selected), + NULL, NULL, + _glade_marshal_VOID__OBJECT, G_TYPE_NONE, 1, G_TYPE_OBJECT); + + /** + * GladeBaseEditor::child-change-type: + * @gladebaseeditor: the #GladeBaseEditor which received the signal. + * @child: the #GObject being changed. + * @type: the new type for @child. + * + * Returns: TRUE to stop signal emission. + */ + glade_base_editor_signals[SIGNAL_CHANGE_TYPE] = + g_signal_new ("change-type", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeBaseEditorClass, change_type), + _glade_boolean_handled_accumulator, NULL, NULL, + G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT, G_TYPE_GTYPE); + + /** + * GladeBaseEditor::get-display-name: + * @gladebaseeditor: the #GladeBaseEditor which received the signal. + * @gchild: the child to get display name string to show in @gladebaseeditor + * treeview. + * + * Returns: a newly allocated string. + */ + glade_base_editor_signals[SIGNAL_GET_DISPLAY_NAME] = + g_signal_new ("get-display-name", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeBaseEditorClass, get_display_name), + _glade_string_accumulator, NULL, + _glade_marshal_STRING__OBJECT, + G_TYPE_STRING, 1, G_TYPE_OBJECT); + + /** + * GladeBaseEditor::build-child: + * @gladebaseeditor: the #GladeBaseEditor which received the signal. + * @gparent: the parent of the new child + * @type: the #GType of the child + * + * Create a child widget here if something else must be done other than + * calling glade_command_create() such as creating an intermediate parent. + * + * Returns: (transfer full) (nullable): the newly created #GladeWidget or + * %NULL if child cant be created + */ + glade_base_editor_signals[SIGNAL_BUILD_CHILD] = + g_signal_new ("build-child", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeBaseEditorClass, build_child), + _glade_stop_emission_accumulator, NULL, NULL, + G_TYPE_OBJECT, 2, G_TYPE_OBJECT, G_TYPE_GTYPE); + + /** + * GladeBaseEditor::delete-child: + * @gladebaseeditor: the #GladeBaseEditor which received the signal. + * @gparent: the parent + * @gchild: the child to delete + */ + glade_base_editor_signals[SIGNAL_DELETE_CHILD] = + g_signal_new ("delete-child", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeBaseEditorClass, delete_child), + _glade_boolean_handled_accumulator, NULL, + _glade_marshal_BOOLEAN__OBJECT_OBJECT, + G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); + + /** + * GladeBaseEditor::move-child: + * @gladebaseeditor: the #GladeBaseEditor which received the signal. + * @gparent: the new parent of @gchild + * @gchild: the #GladeWidget to move + * + * Move child here if something else must be done other than cut & paste. + * + * Returns: whether child has been successfully moved or not. + */ + glade_base_editor_signals[SIGNAL_MOVE_CHILD] = + g_signal_new ("move-child", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeBaseEditorClass, move_child), + _glade_stop_emission_accumulator, NULL, + _glade_marshal_BOOLEAN__OBJECT_OBJECT, + G_TYPE_BOOLEAN, 2, G_TYPE_OBJECT, G_TYPE_OBJECT); + + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-base-editor.ui"); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, paned); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, treeview); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, add_button); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, delete_button); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, help_button); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, table); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, signal_editor); + gtk_widget_class_bind_template_child_private (widget_class, GladeBaseEditor, tip_label); + gtk_widget_class_bind_template_callback (widget_class, glade_base_editor_realize_callback); + gtk_widget_class_bind_template_callback (widget_class, glade_base_editor_treeview_cursor_changed); + gtk_widget_class_bind_template_callback (widget_class, glade_base_editor_popup_handler); + gtk_widget_class_bind_template_callback (widget_class, glade_base_editor_treeview_key_press_event); + gtk_widget_class_bind_template_callback (widget_class, glade_base_editor_add_activate); + gtk_widget_class_bind_template_callback (widget_class, glade_base_editor_delete_activate); +} + +/********************************* Public API *********************************/ +/** + * glade_base_editor_new: + * @container: a container this new editor will edit. + * @main_editable: the custom #GladeEditable for @container, or %NULL + * @...: A NULL terminated list of gchar *, GType + * + * Creates a new GladeBaseEditor with @container toplevel + * support for all the object types indicated in the variable argument list. + * Argument List: + * o The type name + * o The GType the editor will support + * + * Returns: a new GladeBaseEditor. + */ +GladeBaseEditor * +glade_base_editor_new (GObject *container, GladeEditable *main_editable, ...) +{ + ChildTypeTab *child_type; + GladeWidget *gcontainer; + GladeBaseEditor *editor; + GladeBaseEditorPrivate *priv; + GtkTreeIter iter; + GType iter_type; + gchar *name; + va_list args; + + gcontainer = glade_widget_get_from_gobject (container); + g_return_val_if_fail (GLADE_IS_WIDGET (gcontainer), NULL); + + editor = GLADE_BASE_EDITOR (g_object_new (GLADE_TYPE_BASE_EDITOR, NULL)); + priv = glade_base_editor_get_instance_private (editor); + + /* Store */ + priv->model = (GtkTreeModel *) gtk_tree_store_new (GLADE_BASE_EDITOR_N_COLUMNS, + G_TYPE_OBJECT, + G_TYPE_OBJECT, + G_TYPE_STRING, + G_TYPE_STRING, + GTK_TYPE_TREE_MODEL); + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->treeview), priv->model); + gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->treeview)); + + g_signal_connect (priv->model, "row-inserted", + G_CALLBACK (glade_base_editor_row_inserted), editor); + + if (main_editable) + g_warning ("%s main_editable is deprecated, the editor will only show the hierarchy editor", __func__); + + child_type = g_new0 (ChildTypeTab, 1); + child_type->parent_type = G_OBJECT_TYPE (container); + child_type->children = + (GtkTreeModel *) gtk_list_store_new (GLADE_BASE_EDITOR_TYPES_N_COLUMNS, + G_TYPE_GTYPE, G_TYPE_STRING); + + va_start (args, main_editable); + while ((name = va_arg (args, gchar *))) + { + iter_type = va_arg (args, GType); + + gtk_list_store_append (GTK_LIST_STORE (child_type->children), &iter); + gtk_list_store_set (GTK_LIST_STORE (child_type->children), &iter, + GLADE_BASE_EDITOR_GTYPE, iter_type, + GLADE_BASE_EDITOR_CLASS_NAME, name, -1); + + if (priv->add_type == 0) + priv->add_type = iter_type; + } + va_end (args); + + priv->child_types = g_list_prepend (priv->child_types, child_type); + + glade_base_editor_set_container (editor, container); + + glade_signal_editor_load_widget (priv->signal_editor, priv->gcontainer); + + return editor; +} + + + +/** + * glade_base_editor_append_types: + * @editor: A #GladeBaseEditor + * @parent_type: the parent type these child types will apply to + * @...: A NULL terminated list of gchar *, GType + * + * Appends support for all the object types indicated in the variable argument list. + * Argument List: + * o The type name + * o The GType the editor will support for parents of type @type + * + */ +void +glade_base_editor_append_types (GladeBaseEditor *editor, GType parent_type, ...) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + ChildTypeTab *child_type; + GtkTreeIter iter; + gchar *name; + va_list args; + + g_return_if_fail (GLADE_IS_BASE_EDITOR (editor)); + g_return_if_fail (get_children_model_for_type (editor, parent_type) == NULL); + + child_type = g_new0 (ChildTypeTab, 1); + child_type->parent_type = parent_type; + child_type->children = + (GtkTreeModel *) gtk_list_store_new (GLADE_BASE_EDITOR_TYPES_N_COLUMNS, + G_TYPE_GTYPE, G_TYPE_STRING); + + va_start (args, parent_type); + while ((name = va_arg (args, gchar *))) + { + gtk_list_store_append (GTK_LIST_STORE (child_type->children), &iter); + gtk_list_store_set (GTK_LIST_STORE (child_type->children), &iter, + GLADE_BASE_EDITOR_GTYPE, va_arg (args, GType), + GLADE_BASE_EDITOR_CLASS_NAME, name, + -1); + } + va_end (args); + + priv->child_types = + g_list_insert_sorted (priv->child_types, child_type, + (GCompareFunc) sort_type_by_hierarchy); +} + +/** + * glade_base_editor_add_default_properties: + * @editor: a #GladeBaseEditor + * @gchild: a #GladeWidget + * + * Add @gchild name and type property to @editor + * + * NOTE: This function is intended to be used in "child-selected" callbacks + */ +void +glade_base_editor_add_default_properties (GladeBaseEditor *editor, + GladeWidget *gchild) +{ + GtkTreeIter combo_iter; + GtkWidget *label, *entry; + GtkTreeModel *child_class; + GtkCellRenderer *renderer; + GObject *child; + + g_return_if_fail (GLADE_IS_BASE_EDITOR (editor)); + g_return_if_fail (GLADE_IS_WIDGET (gchild)); + g_return_if_fail (GLADE_IS_WIDGET (glade_widget_get_parent (gchild))); + + child = glade_widget_get_object (gchild); + + child_class = + get_children_model_for_child_type (editor, G_OBJECT_TYPE (child)); + + /* Name */ + label = gtk_label_new (_("ID:")); + gtk_widget_set_halign (label, GTK_ALIGN_END); + gtk_widget_set_valign (label, GTK_ALIGN_START); + + entry = gtk_entry_new (); + if (glade_widget_has_name (gchild)) + gtk_entry_set_text (GTK_ENTRY (entry), glade_widget_get_name (gchild)); + else + gtk_entry_set_text (GTK_ENTRY (entry), ""); + + g_object_set_data (G_OBJECT (entry), "editor", editor); + g_signal_connect (entry, "activate", + G_CALLBACK (glade_base_editor_name_activate), gchild); + g_signal_connect (entry, "changed", + G_CALLBACK (glade_base_editor_name_activate), gchild); + glade_base_editor_table_attach (editor, label, entry); + + if (child_class && gtk_tree_model_iter_n_children (child_class, NULL) > 1) + { + /* Type */ + label = gtk_label_new (_("Type:")); + gtk_widget_set_halign (label, GTK_ALIGN_END); + gtk_widget_set_valign (label, GTK_ALIGN_START); + + entry = gtk_combo_box_new (); + gtk_combo_box_set_model (GTK_COMBO_BOX (entry), child_class); + + renderer = gtk_cell_renderer_text_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (entry), renderer, FALSE); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (entry), renderer, "text", + GLADE_BASE_EDITOR_CLASS_NAME, NULL); + + if (glade_base_editor_get_type_info (editor, &combo_iter, + G_OBJECT_TYPE (child), -1)) + gtk_combo_box_set_active_iter (GTK_COMBO_BOX (entry), &combo_iter); + + g_signal_connect (entry, "changed", + G_CALLBACK (glade_base_editor_type_changed), editor); + glade_base_editor_table_attach (editor, label, entry); + } +} + +/** + * glade_base_editor_add_properties: + * @editor: a #GladeBaseEditor + * @gchild: a #GladeWidget + * @packing: whether we are adding packing properties or not + * @...: A NULL terminated list of properties names. + * + * Add @gchild properties to @editor + * + * NOTE: This function is intended to be used in "child-selected" callbacks + */ +void +glade_base_editor_add_properties (GladeBaseEditor *editor, + GladeWidget *gchild, + gboolean packing, + ...) +{ + GladeEditorProperty *eprop; + va_list args; + gchar *property; + + g_return_if_fail (GLADE_IS_BASE_EDITOR (editor)); + g_return_if_fail (GLADE_IS_WIDGET (gchild)); + + va_start (args, packing); + property = va_arg (args, gchar *); + + while (property) + { + eprop = + glade_widget_create_editor_property (gchild, property, packing, TRUE); + if (eprop) + glade_base_editor_table_attach (editor, + glade_editor_property_get_item_label (eprop), + GTK_WIDGET (eprop)); + property = va_arg (args, gchar *); + } + va_end (args); +} + + +/** + * glade_base_editor_add_editable: + * @editor: a #GladeBaseEditor + * @gchild: the #GladeWidget + * @page: the #GladeEditorPageType of the desired page for @gchild + * + * Add @gchild editor of type @page to the base editor + * + * NOTE: This function is intended to be used in "child-selected" callbacks + */ +void +glade_base_editor_add_editable (GladeBaseEditor *editor, + GladeWidget *gchild, + GladeEditorPageType page) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GladeEditable *editable; + gint row; + + g_return_if_fail (GLADE_IS_BASE_EDITOR (editor)); + g_return_if_fail (GLADE_IS_WIDGET (gchild)); + + editable = glade_widget_adaptor_create_editable (glade_widget_get_adaptor (gchild), page); + glade_editable_set_show_name (editable, FALSE); + glade_editable_load (editable, gchild); + gtk_widget_show (GTK_WIDGET (editable)); + + row = priv->row; + + gtk_grid_attach (GTK_GRID (priv->table), GTK_WIDGET (editable), 0, + row, 2, 1); + gtk_widget_set_hexpand (GTK_WIDGET (editable), TRUE); + + priv->row++; + + gtk_widget_hide (priv->tip_label); +} + + + +/** + * glade_base_editor_add_label: + * @editor: a #GladeBaseEditor + * @str: the label string + * + * Adds a new label to @editor + * + * NOTE: This function is intended to be used in "child-selected" callbacks + */ +void +glade_base_editor_add_label (GladeBaseEditor *editor, gchar *str) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkWidget *label; + gchar *markup; + gint row; + + g_return_if_fail (GLADE_IS_BASE_EDITOR (editor)); + g_return_if_fail (str != NULL); + + label = gtk_label_new (NULL); + markup = g_strdup_printf ("%s", str); + row = priv->row; + + gtk_label_set_markup (GTK_LABEL (label), markup); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_widget_set_valign (label, GTK_ALIGN_START); + gtk_widget_set_margin_top (label, 6); + gtk_widget_set_margin_bottom (label, 6); + + gtk_grid_attach (GTK_GRID (priv->table), label, 0, row, 2, 1); + gtk_widget_show (label); + priv->row++; + + gtk_widget_hide (priv->tip_label); + g_free (markup); +} + +/** + * glade_base_editor_set_show_signal_editor: + * @editor: a #GladeBaseEditor + * @val: whether to show the signal editor + * + * Shows/hide @editor 's signal editor + */ +void +glade_base_editor_set_show_signal_editor (GladeBaseEditor *editor, gboolean val) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + + g_return_if_fail (GLADE_IS_BASE_EDITOR (editor)); + + if (val) + gtk_widget_show (GTK_WIDGET (priv->signal_editor)); + else + gtk_widget_hide (GTK_WIDGET (priv->signal_editor)); +} + +/* Convenience functions */ + +static void +glade_base_editor_help (GtkButton *button, gchar *markup) +{ + GtkWidget *dialog; + + dialog = gtk_message_dialog_new (GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (button))), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_INFO, GTK_BUTTONS_OK, " "); + + gtk_message_dialog_set_markup (GTK_MESSAGE_DIALOG (dialog), markup); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + +/** + * glade_base_editor_pack_new_window: + * @editor: a #GladeBaseEditor + * @title: the window title + * @help_markup: the help text + * + * This convenience function create a new dialog window and packs @editor in it. + * + * Returns: (transfer full): the newly created window + */ +GtkWidget * +glade_base_editor_pack_new_window (GladeBaseEditor *editor, + gchar *title, + gchar *help_markup) +{ + GladeBaseEditorPrivate *priv = glade_base_editor_get_instance_private (editor); + GtkWidget *window, *headerbar; + gchar *msg; + + g_return_val_if_fail (GLADE_IS_BASE_EDITOR (editor), NULL); + + /* Window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + headerbar = gtk_header_bar_new (); + gtk_header_bar_set_show_close_button (GTK_HEADER_BAR (headerbar), TRUE); + gtk_window_set_titlebar (GTK_WINDOW (window), headerbar); + gtk_widget_show (headerbar); + + if (title) + { + const gchar *name = glade_widget_get_display_name (priv->gcontainer); + + gtk_header_bar_set_title (GTK_HEADER_BAR (headerbar), title); + gtk_header_bar_set_subtitle (GTK_HEADER_BAR (headerbar), name); + } + + g_signal_connect_swapped (G_OBJECT (editor), "notify::container", + G_CALLBACK (gtk_widget_destroy), window); + + msg = help_markup ? help_markup : + _("Tips:\n" + " * Right-click over the treeview to add items.\n" + " * Press Delete to remove the selected item.\n" + " * Drag & Drop to reorder.\n" + " * Type column is editable."); + + gtk_label_set_markup (GTK_LABEL (priv->tip_label), msg); + g_signal_connect (priv->help_button, "clicked", + G_CALLBACK (glade_base_editor_help), + msg); + + gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (editor)); + gtk_widget_show_all (GTK_WIDGET (editor)); + + gtk_window_set_default_size (GTK_WINDOW (window), 640, 480); + + return window; +} diff --git a/gladeui/glade-base-editor.h b/gladeui/glade-base-editor.h new file mode 100644 index 0000000..5d53aae --- /dev/null +++ b/gladeui/glade-base-editor.h @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2006 Juan Pablo Ugarte. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Juan Pablo Ugarte + */ +#ifndef __GLADE_BASE_EDITOR_H__ +#define __GLADE_BASE_EDITOR_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_BASE_EDITOR glade_base_editor_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeBaseEditor, glade_base_editor, GLADE, BASE_EDITOR, GtkBox) + +struct _GladeBaseEditorClass +{ + GtkBoxClass parent_class; + + void (*child_selected) (GladeBaseEditor *editor, GladeWidget *gchild); + gboolean (*change_type) (GladeBaseEditor *editor, GladeWidget *gchild, GType type); + gchar * (*get_display_name) (GladeBaseEditor *editor, GladeWidget *gchild); + GladeWidget * (*build_child) (GladeBaseEditor *editor, GladeWidget *parent, GType type); + gboolean (*delete_child) (GladeBaseEditor *editor, GladeWidget *parent, GladeWidget *gchild); + gboolean (*move_child) (GladeBaseEditor *editor, GladeWidget *gparent, GladeWidget *gchild); + + gpointer padding[6]; +}; + +GladeBaseEditor *glade_base_editor_new (GObject *container, + GladeEditable *main_editable, + ...); + +void glade_base_editor_append_types (GladeBaseEditor *editor, + GType parent_type, + ...); + +void glade_base_editor_add_editable (GladeBaseEditor *editor, + GladeWidget *gchild, + GladeEditorPageType page); + +void glade_base_editor_add_default_properties (GladeBaseEditor *editor, + GladeWidget *gchild); + +void glade_base_editor_add_properties (GladeBaseEditor *editor, + GladeWidget *gchild, + gboolean packing, + ...); + +void glade_base_editor_add_label (GladeBaseEditor *editor, + gchar *str); + +void glade_base_editor_set_show_signal_editor (GladeBaseEditor *editor, + gboolean val); + +/* Convenience functions */ +GtkWidget *glade_base_editor_pack_new_window (GladeBaseEditor *editor, + gchar *title, + gchar *help_markup); + +G_END_DECLS + +#endif /* __GLADE_BASE_EDITOR_H__ */ diff --git a/gladeui/glade-base-editor.ui b/gladeui/glade-base-editor.ui new file mode 100644 index 0000000..bcae0a4 --- /dev/null +++ b/gladeui/glade-base-editor.ui @@ -0,0 +1,259 @@ + + + + + + + diff --git a/gladeui/glade-builtins.c b/gladeui/glade-builtins.c new file mode 100644 index 0000000..beadc5c --- /dev/null +++ b/gladeui/glade-builtins.c @@ -0,0 +1,653 @@ +/* + * glade-clipboard.c - An object for handling Cut/Copy/Paste. + * + * Copyright (C) 2005 The GNOME Foundation. + * + * Author(s): + * Tristan Van Berkom + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include "glade-builtins.h" +#include "glade-displayable-values.h" + + +struct _GladeParamSpecObjects +{ + GParamSpec parent_instance; + + GType type; /* Object or interface type accepted + * in this object list. + */ +}; + +typedef struct _GladeStockItem +{ + gchar *value_name; + gchar *value_nick; + gchar *clean_name; + gint value; +} GladeStockItem; + + +/************************************************************ + * Auto-generate the enum type for stock properties * + ************************************************************/ + +/* Hard-coded list of stock images (and displayable translations) from gtk+ that are not stock "items" */ +static const gchar *builtin_stock_images[] = { + "gtk-dialog-authentication", /* GTK_STOCK_DIALOG_AUTHENTICATION */ + "gtk-dnd", /* GTK_STOCK_DND */ + "gtk-dnd-multiple", /* GTK_STOCK_DND_MULTIPLE */ + "gtk-color-picker", /* GTK_STOCK_COLOR_PICKER */ + "gtk-directory", /* GTK_STOCK_DIRECTORY */ + "gtk-file", /* GTK_STOCK_FILE */ + "gtk-missing-image" /* GTK_STOCK_MISSING_IMAGE */ +}; + +static const gchar *builtin_stock_displayables[] = { + /* GTK_STOCK_DIALOG_AUTHENTICATION */ + N_("Authentication"), + /* GTK_STOCK_DND */ + N_("Drag and Drop"), + /* GTK_STOCK_DND_MULTIPLE */ + N_("Drag and Drop Multiple"), + /* GTK_STOCK_COLOR_PICKER */ + N_("Color Picker"), + /* GTK_STOCK_DIRECTORY */ + N_("Directory"), + /* GTK_STOCK_FILE */ + N_("File"), + /* GTK_STOCK_MISSING_IMAGE */ + N_("Missing Image") +}; + +static GSList *stock_prefixs = NULL; +static gboolean stock_prefixs_done = FALSE; + +/* FIXME: func needs documentation + */ +void +glade_standard_stock_append_prefix (const gchar * prefix) +{ + if (stock_prefixs_done) + { + g_warning + ("glade_standard_stock_append_prefix should be used in catalog init-function"); + return; + } + + stock_prefixs = g_slist_append (stock_prefixs, g_strdup (prefix)); +} + +static GladeStockItem * +new_from_values (const gchar * name, const gchar * nick, gint value) +{ + GladeStockItem *new_gsi = NULL; + gchar *clean_name; + size_t len = 0; + guint i = 0; + guint j = 0; + + new_gsi = (GladeStockItem *) g_malloc0 (sizeof (GladeStockItem)); + + new_gsi->value_name = g_strdup (name); + new_gsi->value_nick = g_strdup (nick); + new_gsi->value = value; + + + clean_name = g_strdup (name); + len = strlen (clean_name); + + while (i + j <= len) + { + if (clean_name[i + j] == '_') + j++; + + clean_name[i] = clean_name[i + j]; + i++; + } + + new_gsi->clean_name = g_utf8_collate_key (clean_name, i - j); + + g_free (clean_name); + + return new_gsi; +} + + +static gint +compare_two_gsi (gconstpointer a, gconstpointer b) +{ + GladeStockItem *gsi1 = (GladeStockItem *) a; + GladeStockItem *gsi2 = (GladeStockItem *) b; + + return strcmp (gsi1->clean_name, gsi2->clean_name); +} + +static GArray * +list_stock_items (gboolean include_images) +{ + GtkStockItem item; + GSList *l = NULL, *stock_list = NULL, *p = NULL; + gchar *stock_id = NULL, *prefix = NULL; + gint stock_enum = 0, i = 0; + GEnumValue value; + GArray *values = NULL; + GladeStockItem *gsi; + GSList *gsi_list = NULL; + GSList *gsi_list_list = NULL; + + if (gdk_display_get_default () == NULL) + { + values = g_array_sized_new (TRUE, TRUE, sizeof (GEnumValue), 1); + + value.value = 0; + value.value_name = "dummy"; + value.value_nick = "Dummy"; + g_array_append_val (values, value); + + return values; + } + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + stock_list = g_slist_reverse (gtk_stock_list_ids ()); +G_GNUC_END_IGNORE_DEPRECATIONS + + values = g_array_sized_new (TRUE, TRUE, sizeof (GEnumValue), + g_slist_length (stock_list)); + + /* We want gtk+ stock items to appear first */ + if ((stock_prefixs && strcmp (stock_prefixs->data, "gtk-")) || + stock_prefixs == NULL) + stock_prefixs = g_slist_prepend (stock_prefixs, g_strdup ("gtk-")); + + for (p = stock_prefixs; p; p = g_slist_next (p)) + { + prefix = p->data; + + for (l = stock_list; l; l = g_slist_next (l)) + { + stock_id = l->data; +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + if (g_str_has_prefix (stock_id, prefix) == FALSE || + gtk_stock_lookup (stock_id, &item) == FALSE) + continue; +G_GNUC_END_IGNORE_DEPRECATIONS + + gsi = new_from_values (item.label, stock_id, stock_enum++); + gsi_list = + g_slist_insert_sorted (gsi_list, gsi, + (GCompareFunc) compare_two_gsi); + } + + gsi_list_list = g_slist_append (gsi_list_list, gsi_list); + gsi_list = NULL; + + /* Images are appended after the gtk+ group of items */ + if (include_images && !strcmp (prefix, "gtk-")) + { + for (i = 0; i < G_N_ELEMENTS (builtin_stock_images); i++) + { + gsi = + new_from_values (builtin_stock_images[i], + builtin_stock_images[i], stock_enum++); + gsi_list = + g_slist_insert_sorted (gsi_list, gsi, + (GCompareFunc) compare_two_gsi); + } + gsi_list_list = g_slist_append (gsi_list_list, gsi_list); + gsi_list = NULL; + } + } + + for (p = gsi_list_list; p; p = g_slist_next (p)) + { + + for (l = (GSList *) p->data; l; l = g_slist_next (l)) + { + gsi = (GladeStockItem *) l->data; + value.value = gsi->value; + value.value_name = g_strdup (gsi->value_name); + value.value_nick = g_strdup (gsi->value_nick); + values = g_array_append_val (values, value); + + g_free (gsi->value_nick); + g_free (gsi->value_name); + g_free (gsi->clean_name); + g_free (gsi); + } + g_slist_free ((GSList *) p->data); + } + + g_slist_free (gsi_list_list); + + stock_prefixs_done = TRUE; + g_slist_free_full (stock_list, g_free); + + return values; +} + +static gchar * +clean_stock_name (const gchar * name) +{ + gchar *clean_name, *str; + size_t len = 0; + guint i = 0; + guint j = 0; + + g_assert (name && name[0]); + + str = g_strdup (name); + len = strlen (str); + + while (i + j <= len) + { + if (str[i + j] == '_') + j++; + + str[i] = str[i + j]; + i++; + } + clean_name = g_strndup (str, i - j); + g_free (str); + + return clean_name; +} + +GType +glade_standard_stock_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) + { + GArray *values = list_stock_items (FALSE); + gint i, n_values = values->len; + GEnumValue *enum_values = (GEnumValue *) values->data; + GtkStockItem item; + + etype = g_enum_register_static ("GladeStock", + (GEnumValue *) g_array_free (values, + FALSE)); + + if (gdk_display_get_default () == NULL) + return etype; + + /* Register displayable by GType, i.e. after the types been created. */ + for (i = 0; i < n_values; i++) + { +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gboolean valid_item = gtk_stock_lookup (enum_values[i].value_nick, &item); +G_GNUC_END_IGNORE_DEPRECATIONS + + if (valid_item) + { + gchar *clean_name = clean_stock_name (item.label); + + if (!glade_get_displayable_value (etype, enum_values[i].value_nick)) + glade_register_translated_value (etype, enum_values[i].value_nick, clean_name); + g_free (clean_name); + } + } + } + return etype; +} + + +GType +glade_standard_stock_image_get_type (void) +{ + static GType etype = 0; + + if (etype == 0) + { + GArray *values = list_stock_items (TRUE); + gint i, n_values = values->len; + GEnumValue *enum_values = (GEnumValue *) values->data; + GtkStockItem item; + + etype = g_enum_register_static ("GladeStockImage", + (GEnumValue *) g_array_free (values, + FALSE)); + + if (gdk_display_get_default () == NULL) + return etype; + + /* Register displayable by GType, i.e. after the types been created. */ + for (i = 0; i < n_values; i++) + { +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + gboolean valid_item = gtk_stock_lookup (enum_values[i].value_nick, &item); +G_GNUC_END_IGNORE_DEPRECATIONS + if (valid_item) + { + gchar *clean_name = clean_stock_name (item.label); + + /* These are translated, we just cut out the mnemonic underscores */ + if (!glade_get_displayable_value (etype, enum_values[i].value_nick)) + glade_register_translated_value (etype, enum_values[i].value_nick, clean_name); + g_free (clean_name); + } + } + + for (i = 0; i < G_N_ELEMENTS (builtin_stock_images); i++) + { + /* these ones are translated from glade */ + if (!glade_get_displayable_value (etype, builtin_stock_images[i])) + glade_register_displayable_value (etype, + builtin_stock_images[i], + GETTEXT_PACKAGE, + builtin_stock_displayables[i]); + } + } + return etype; +} + +/** + * glade_standard_stock_spec: + * + * Returns: (transfer full): a #GParamSpec describing a list of builtin stock item + */ +GParamSpec * +glade_standard_stock_spec (void) +{ + return g_param_spec_enum ("stock", _("Stock"), + _("A builtin stock item"), + GLADE_TYPE_STOCK, 0, G_PARAM_READWRITE); +} + +/** + * glade_standard_stock_image_spec: + * + * Returns: (transfer full): a #GParamSpec describing a list of builtin stock image + */ +GParamSpec * +glade_standard_stock_image_spec (void) +{ + return g_param_spec_enum ("stock-image", _("Stock Image"), + _("A builtin stock image"), + GLADE_TYPE_STOCK_IMAGE, 0, G_PARAM_READWRITE); +} + +/**************************************************************** + * A GList boxed type used by GladeParamSpecObjects and * + * GladeParamSpecAccel (which is now in the glade-gtk backend) * + ****************************************************************/ +GType +glade_glist_get_type (void) +{ + static GType type_id = 0; + + if (!type_id) + type_id = g_boxed_type_register_static + ("GladeGList", + (GBoxedCopyFunc) g_list_copy, (GBoxedFreeFunc) g_list_free); + return type_id; +} + +/**************************************************************** + * Built-in GladeParamSpecObjects for object list properties * + * (Used as a pspec to describe an AtkRelationSet, but can * + * for any object list property) * + ****************************************************************/ +static void +param_objects_init (GParamSpec * pspec) +{ + GladeParamSpecObjects *ospec = GLADE_PARAM_SPEC_OBJECTS (pspec); + ospec->type = G_TYPE_OBJECT; +} + +static void +param_objects_set_default (GParamSpec * pspec, GValue * value) +{ + if (value->data[0].v_pointer != NULL) + { + g_free (value->data[0].v_pointer); + } + value->data[0].v_pointer = NULL; +} + +static gboolean +param_objects_validate (GParamSpec * pspec, GValue * value) +{ + GladeParamSpecObjects *ospec = GLADE_PARAM_SPEC_OBJECTS (pspec); + GList *objects, *list, *toremove = NULL; + GObject *object; + + objects = value->data[0].v_pointer; + + for (list = objects; list; list = list->next) + { + object = list->data; + + if (!(G_OBJECT_TYPE (object) == ospec->type || + g_type_is_a (G_OBJECT_TYPE (object), ospec->type))) + toremove = g_list_prepend (toremove, object); + } + + for (list = toremove; list; list = list->next) + { + object = list->data; + objects = g_list_remove (objects, object); + } + if (toremove) + g_list_free (toremove); + + value->data[0].v_pointer = objects; + + return toremove != NULL; +} + +static gint +param_objects_values_cmp (GParamSpec * pspec, + const GValue * value1, const GValue * value2) +{ + guint8 *p1 = value1->data[0].v_pointer; + guint8 *p2 = value2->data[0].v_pointer; + + /* not much to compare here, try to at least provide stable lesser/greater result */ + + return p1 < p2 ? -1 : p1 > p2; +} + +GType +glade_param_objects_get_type (void) +{ + static GType objects_type = 0; + + if (objects_type == 0) + { + static /* const */ GParamSpecTypeInfo pspec_info = { + sizeof (GladeParamSpecObjects), /* instance_size */ + 16, /* n_preallocs */ + param_objects_init, /* instance_init */ + 0xdeadbeef, /* value_type, assigned further down */ + NULL, /* finalize */ + param_objects_set_default, /* value_set_default */ + param_objects_validate, /* value_validate */ + param_objects_values_cmp, /* values_cmp */ + }; + pspec_info.value_type = GLADE_TYPE_GLIST; + + objects_type = g_param_type_register_static + ("GladeParamObjects", &pspec_info); + } + return objects_type; +} + +/** + * glade_param_spec_objects: + * + * Returns: (transfer full): a #GParamSpec describing a list of #GObject + */ +GParamSpec * +glade_param_spec_objects (const gchar * name, + const gchar * nick, + const gchar * blurb, + GType accepted_type, GParamFlags flags) +{ + GladeParamSpecObjects *pspec; + + pspec = g_param_spec_internal (GLADE_TYPE_PARAM_OBJECTS, + name, nick, blurb, flags); + + pspec->type = accepted_type; + return G_PARAM_SPEC (pspec); +} + +void +glade_param_spec_objects_set_type (GladeParamSpecObjects * pspec, GType type) +{ + pspec->type = type; +} + +GType +glade_param_spec_objects_get_type (GladeParamSpecObjects * pspec) +{ + return pspec->type; +} + +/** + * glade_standard_objects_spec: + * + * This was developed for the purpose of holding a list + * of 'targets' in an AtkRelation (we are setting it up + * as a property) + * + * Returns: (transfer full): a #GParamSpec describing a list of #GObject + */ +GParamSpec * +glade_standard_objects_spec (void) +{ + return glade_param_spec_objects ("objects", _("Objects"), + _("A list of objects"), + G_TYPE_OBJECT, G_PARAM_READWRITE); +} + +/** + * glade_standard_pixbuf_spec: + * + * Returns: (transfer full): a #GParamSpec describing a #GdkPixbuf + */ +GParamSpec * +glade_standard_pixbuf_spec (void) +{ + return g_param_spec_object ("pixbuf", _("Image File Name"), + _("Enter a filename, relative path or full path to " + "load the image"), GDK_TYPE_PIXBUF, + G_PARAM_READWRITE); +} + +/** + * glade_standard_gdkcolor_spec: + * + * Returns: (transfer full): a #GParamSpec describing a #GdkColor + */ +GParamSpec * +glade_standard_gdkcolor_spec (void) +{ + return g_param_spec_boxed ("gdkcolor", _("Color"), + _("A GDK color value"), GDK_TYPE_COLOR, + G_PARAM_READWRITE); +} + +/**************************************************************** + * Basic types follow * + ****************************************************************/ + +/** + * glade_standard_int_spec: + * + * Returns: (transfer full): a #GParamSpec describing an int + */ +GParamSpec * +glade_standard_int_spec (void) +{ + return g_param_spec_int ("int", "Integer", + "An integer value", + G_MININT, G_MAXINT, 0, G_PARAM_READWRITE); +} + +/** + * glade_standard_uint_spec: + * + * Returns: (transfer full): a #GParamSpec describing an uint + */ +GParamSpec * +glade_standard_uint_spec (void) +{ + return g_param_spec_uint ("uint", "Unsigned Integer", + "An unsigned integer value", + 0, G_MAXUINT, 0, G_PARAM_READWRITE); +} + +/** + * glade_standard_string_spec: + * + * Returns: (transfer full): a #GParamSpec describing a string + */ +GParamSpec * +glade_standard_string_spec (void) +{ + return g_param_spec_string ("string", _("String"), + _("An entry"), "", G_PARAM_READWRITE); +} + +/** + * glade_standard_strv_spec: + * + * Returns: (transfer full): a #GParamSpec describing an array of strings + */ +GParamSpec * +glade_standard_strv_spec (void) +{ + return g_param_spec_boxed ("strv", "Strv", + "String array", G_TYPE_STRV, G_PARAM_READWRITE); +} + +/** + * glade_standard_float_spec: + * + * Returns: (transfer full): a #GParamSpec describing a float + */ +GParamSpec * +glade_standard_float_spec (void) +{ + return g_param_spec_float ("float", "Float", + "A floating point entry", + 0.0F, G_MAXFLOAT, 0.0F, G_PARAM_READWRITE); +} + +/** + * glade_standard_boolean_spec: + * + * Returns: (transfer full): a #GParamSpec describing a boolean + */ +GParamSpec * +glade_standard_boolean_spec (void) +{ + return g_param_spec_boolean ("boolean", "Boolean", + "A boolean value", FALSE, G_PARAM_READWRITE); +} diff --git a/gladeui/glade-builtins.h b/gladeui/glade-builtins.h new file mode 100644 index 0000000..5837900 --- /dev/null +++ b/gladeui/glade-builtins.h @@ -0,0 +1,61 @@ +#ifndef __GLADE_BUILTINS_H__ +#define __GLADE_BUILTINS_H__ + +#include +#include + +G_BEGIN_DECLS + +typedef struct _GladeParamSpecObjects GladeParamSpecObjects; + + +#define GLADE_TYPE_STOCK (glade_standard_stock_get_type()) +#define GLADE_TYPE_STOCK_IMAGE (glade_standard_stock_image_get_type()) +#define GLADE_TYPE_GLIST (glade_glist_get_type()) +#define GLADE_TYPE_PARAM_OBJECTS (glade_param_objects_get_type()) + +#define GLADE_IS_STOCK(pspec) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GLADE_TYPE_STOCK)) + +#define GLADE_IS_STOCK_IMAGE(pspec) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), GLADE_TYPE_STOCK_IMAGE)) + +#define GLADE_IS_PARAM_SPEC_OBJECTS(pspec) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((pspec), \ + GLADE_TYPE_PARAM_OBJECTS)) +#define GLADE_PARAM_SPEC_OBJECTS(pspec) \ + (G_TYPE_CHECK_INSTANCE_CAST ((pspec), \ + GLADE_TYPE_PARAM_OBJECTS, GladeParamSpecObjects)) + +GType glade_standard_stock_get_type (void) G_GNUC_CONST; +GType glade_standard_stock_image_get_type (void) G_GNUC_CONST; +GType glade_glist_get_type (void) G_GNUC_CONST; +GType glade_param_objects_get_type (void) G_GNUC_CONST; + +GParamSpec *glade_param_spec_objects (const gchar *name, + const gchar *nick, + const gchar *blurb, + GType accepted_type, + GParamFlags flags); + +void glade_param_spec_objects_set_type (GladeParamSpecObjects *pspec, + GType type); +GType glade_param_spec_objects_get_type (GladeParamSpecObjects *pspec); + +GParamSpec *glade_standard_pixbuf_spec (void); +GParamSpec *glade_standard_gdkcolor_spec (void); +GParamSpec *glade_standard_objects_spec (void); +GParamSpec *glade_standard_stock_spec (void); +GParamSpec *glade_standard_stock_image_spec (void); +GParamSpec *glade_standard_int_spec (void); +GParamSpec *glade_standard_uint_spec (void); +GParamSpec *glade_standard_string_spec (void); +GParamSpec *glade_standard_strv_spec (void); +GParamSpec *glade_standard_float_spec (void); +GParamSpec *glade_standard_boolean_spec (void); + +void glade_standard_stock_append_prefix (const gchar *prefix); + +G_END_DECLS + +#endif /* __GLADE_BUILTINS_H__ */ diff --git a/gladeui/glade-catalog.c b/gladeui/glade-catalog.c new file mode 100644 index 0000000..6c0b95b --- /dev/null +++ b/gladeui/glade-catalog.c @@ -0,0 +1,1098 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2004 Imendio AB + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Chema Celorio + */ + +#include + +#include "glade.h" +#include "glade-catalog.h" +#include "glade-widget-adaptor.h" +#include "glade-private.h" +#include "glade-tsort.h" + +#include +#include +#include +#include +#include + +struct _GladeCatalog +{ + guint16 major_version; /* The catalog version */ + guint16 minor_version; + + GList *targetable_versions; /* list of suitable version targets */ + + gchar *library; /* Library name for backend support */ + + gchar *name; /* Symbolic catalog name */ + + gchar *prefix; /* Catalog path prefix */ + + gchar *dep_catalog; /* Symbolic name of the catalog that + * this catalog depends on */ + + gchar *domain; /* The domain to be used to translate + * strings from this catalog (otherwise this + * defaults to the library name) + */ + + gchar *book; /* Devhelp search domain */ + + gchar *icon_prefix; /* the prefix for icons */ + + GList *widget_groups; /* List of widget groups (palette) */ + GList *adaptors; /* List of widget class adaptors (all) */ + + GladeXmlContext *context; /* Xml context is stored after open + * before classes are loaded */ + + GModule *module; + + gchar *init_function_name; /* Catalog's init function name */ + GladeCatalogInitFunc init_function; + + GHashTable *monitors; /* Dir monitors for user templates catalogs */ +}; + +struct _GladeWidgetGroup +{ + gchar *name; /* Group name */ + gchar *title; /* Group name in the palette */ + + gboolean expanded; /* Whether group is expanded in the palette */ + + GList *adaptors; /* List of class adaptors in the palette */ +}; + +/* List of catalogs successfully loaded. */ +static GList *loaded_catalogs = NULL; + +/* Extra paths to load catalogs from */ +static GList *catalog_paths = NULL; + +static gboolean +catalog_get_function (GladeCatalog *catalog, + const gchar *symbol_name, + gpointer *symbol_ptr) +{ + if (catalog->module == NULL) + catalog->module = glade_util_load_library (catalog->library); + + if (catalog->module) + return g_module_symbol (catalog->module, symbol_name, symbol_ptr); + + return FALSE; +} + +static GladeCatalog * +catalog_allocate (void) +{ + GladeCatalog *catalog; + + catalog = g_slice_new0 (GladeCatalog); + + catalog->library = NULL; + catalog->name = NULL; + catalog->prefix = NULL; + catalog->dep_catalog = NULL; + catalog->domain = NULL; + catalog->book = NULL; + catalog->icon_prefix = NULL; + catalog->init_function_name = NULL; + catalog->module = NULL; + + catalog->context = NULL; + catalog->adaptors = NULL; + catalog->widget_groups = NULL; + + catalog->monitors = NULL; + + return catalog; +} + +static void +widget_group_destroy (GladeWidgetGroup *group) +{ + g_return_if_fail (GLADE_IS_WIDGET_GROUP (group)); + + g_free (group->name); + g_free (group->title); + g_list_free (group->adaptors); + + g_slice_free (GladeWidgetGroup, group); +} + +static void +catalog_destroy (GladeCatalog *catalog) +{ + g_return_if_fail (GLADE_IS_CATALOG (catalog)); + + g_free (catalog->name); + g_free (catalog->library); + g_free (catalog->dep_catalog); + g_free (catalog->domain); + g_free (catalog->book); + g_free (catalog->icon_prefix); + g_free (catalog->init_function_name); + + if (catalog->adaptors) + g_list_free (catalog->adaptors); + + if (catalog->widget_groups) + g_list_free_full (catalog->widget_groups, (GDestroyNotify) widget_group_destroy); + + g_clear_pointer (&catalog->context, glade_xml_context_free); + g_slice_free (GladeCatalog, catalog); + + if (catalog->monitors) + g_hash_table_destroy (catalog->monitors); +} + +static GladeCatalog * +catalog_open (const gchar *filename) +{ + GladeTargetableVersion *version; + GladeCatalog *catalog; + GladeXmlContext *context; + GladeXmlDoc *doc; + GladeXmlNode *root; + gchar *name; + + /* get the context & root node of the catalog file */ + context = glade_xml_context_new_from_path (filename, + NULL, GLADE_TAG_GLADE_CATALOG); + if (!context) + { + g_warning ("Couldn't open catalog [%s].", filename); + return NULL; + } + + doc = glade_xml_context_get_doc (context); + root = glade_xml_doc_get_root (doc); + + if (!glade_xml_node_verify (root, GLADE_TAG_GLADE_CATALOG)) + { + g_warning ("Catalog root node is not '%s', skipping %s", + GLADE_TAG_GLADE_CATALOG, filename); + glade_xml_context_free (context); + return NULL; + } + + if (!(name = glade_xml_get_property_string_required (root, GLADE_TAG_NAME, NULL))) + return NULL; + + catalog = catalog_allocate (); + catalog->context = context; + catalog->name = name; + catalog->prefix = g_path_get_dirname (filename); + + glade_xml_get_property_version (root, GLADE_TAG_VERSION, + &catalog->major_version, + &catalog->minor_version); + + /* Make one default suitable target */ + version = g_new (GladeTargetableVersion, 1); + version->major = catalog->major_version; + version->minor = catalog->minor_version; + + catalog->targetable_versions = + glade_xml_get_property_targetable_versions (root, GLADE_TAG_TARGETABLE); + + catalog->targetable_versions = + g_list_prepend (catalog->targetable_versions, version); + + catalog->library = glade_xml_get_property_string (root, GLADE_TAG_LIBRARY); + catalog->dep_catalog = + glade_xml_get_property_string (root, GLADE_TAG_DEPENDS); + catalog->domain = glade_xml_get_property_string (root, GLADE_TAG_DOMAIN); + catalog->book = glade_xml_get_property_string (root, GLADE_TAG_BOOK); + catalog->icon_prefix = + glade_xml_get_property_string (root, GLADE_TAG_ICON_PREFIX); + catalog->init_function_name = + glade_xml_get_value_string (root, GLADE_TAG_INIT_FUNCTION); + + if (!catalog->domain) + catalog->domain = g_strdup (catalog->library); + + /* catalog->icon_prefix defaults to catalog->name */ + if (!catalog->icon_prefix) + catalog->icon_prefix = g_strdup (catalog->name); + + if (catalog->init_function_name) + { + if (!catalog_get_function (catalog, catalog->init_function_name, + (gpointer) & catalog->init_function)) + g_warning ("Failed to find and execute catalog '%s' init function '%s'", + glade_catalog_get_name (catalog), + catalog->init_function_name); + } + + return catalog; +} + +static GHashTable *modules = NULL; + +static GModule * +catalog_load_library (GladeCatalog *catalog) +{ + GModule *module; + + if (modules == NULL) + modules = g_hash_table_new_full (g_str_hash, + g_str_equal, + (GDestroyNotify) g_free, + (GDestroyNotify) g_module_close); + + if (catalog->library == NULL) + return NULL; + + if ((module = g_hash_table_lookup (modules, catalog->library))) + return module; + + if ((module = glade_util_load_library (catalog->library))) + g_hash_table_insert (modules, g_strdup (catalog->library), module); + else + g_warning ("Failed to load external library '%s' for catalog '%s'", + catalog->library, + glade_catalog_get_name (catalog)); + + return module; +} + +static gboolean +catalog_load_classes (GladeCatalog *catalog, GladeXmlNode *widgets_node) +{ + GladeXmlNode *node; + GModule *module = catalog_load_library (catalog); + + node = glade_xml_node_get_children (widgets_node); + for (; node; node = glade_xml_node_next (node)) + { + const gchar *node_name; + GladeWidgetAdaptor *adaptor; + + node_name = glade_xml_node_get_name (node); + if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_CLASS) != 0) + continue; + + adaptor = glade_widget_adaptor_from_catalog (catalog, node, module); + + catalog->adaptors = g_list_prepend (catalog->adaptors, adaptor); + } + + return TRUE; +} + +static gboolean +catalog_load_group (GladeCatalog *catalog, GladeXmlNode *group_node) +{ + GladeWidgetGroup *group; + GladeXmlNode *node; + char *title, *translated_title; + + group = g_slice_new0 (GladeWidgetGroup); + + group->name = glade_xml_get_property_string (group_node, GLADE_TAG_NAME); + + if (!group->name) + { + g_warning ("Required property 'name' not found in group node"); + widget_group_destroy (group); + return FALSE; + } + + title = glade_xml_get_property_string (group_node, GLADE_TAG_TITLE); + if (!title) + { + g_warning ("Required property 'title' not found in group node"); + widget_group_destroy (group); + return FALSE; + } + + /* by default, group is expanded in palette */ + group->expanded = TRUE; + + /* Translate it */ + translated_title = dgettext (catalog->domain, title); + if (translated_title != title) + { + group->title = g_strdup (translated_title); + g_free (title); + } + else + { + group->title = title; + } + + group->adaptors = NULL; + + node = glade_xml_node_get_children (group_node); + for (; node; node = glade_xml_node_next (node)) + { + const gchar *node_name; + GladeWidgetAdaptor *adaptor; + gchar *name; + + node_name = glade_xml_node_get_name (node); + + if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_CLASS_REF) == 0) + { + if ((name = + glade_xml_get_property_string (node, GLADE_TAG_NAME)) == NULL) + { + g_warning ("Couldn't find required property on %s", + GLADE_TAG_GLADE_WIDGET_CLASS); + continue; + } + + if ((adaptor = glade_widget_adaptor_get_by_name (name)) == NULL) + { + g_warning ("Tried to include undefined widget " + "class '%s' in a widget group", name); + g_free (name); + continue; + } + g_free (name); + + group->adaptors = g_list_prepend (group->adaptors, adaptor); + + } + else if (strcmp (node_name, GLADE_TAG_DEFAULT_PALETTE_STATE) == 0) + { + group->expanded = + glade_xml_get_property_boolean + (node, GLADE_TAG_EXPANDED, group->expanded); + } + } + + group->adaptors = g_list_reverse (group->adaptors); + + catalog->widget_groups = g_list_prepend (catalog->widget_groups, group); + + return TRUE; +} + +static void +catalog_load (GladeCatalog *catalog) +{ + GladeXmlDoc *doc; + GladeXmlNode *root; + GladeXmlNode *node; + + g_return_if_fail (catalog->context != NULL); + + doc = glade_xml_context_get_doc (catalog->context); + root = glade_xml_doc_get_root (doc); + node = glade_xml_node_get_children (root); + + for (; node; node = glade_xml_node_next (node)) + { + const gchar *node_name; + + node_name = glade_xml_node_get_name (node); + if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_CLASSES) == 0) + { + catalog_load_classes (catalog, node); + } + else if (strcmp (node_name, GLADE_TAG_GLADE_WIDGET_GROUP) == 0) + { + catalog_load_group (catalog, node); + } + else + continue; + } + + catalog->widget_groups = g_list_reverse (catalog->widget_groups); + g_clear_pointer (&catalog->context, glade_xml_context_free); + + return; +} + +static GladeCatalog * +catalog_find_by_name (GList *catalogs, const gchar *name) +{ + if (name) + { + GList *l; + for (l = catalogs; l; l = g_list_next (l)) + { + GladeCatalog *catalog = l->data; + if (g_strcmp0 (catalog->name, name) == 0) + return catalog; + } + } + + return NULL; +} + +static gint +catalog_name_cmp (gconstpointer a, gconstpointer b) +{ + return (a && b) ? g_strcmp0 (GLADE_CATALOG(a)->name, GLADE_CATALOG(b)->name) : 0; +} + +static GList * +glade_catalog_tsort (GList *catalogs, gboolean loading) +{ + GList *l, *sorted = NULL; + GList *deps = NULL; + + /* Sort alphabetically first */ + catalogs = g_list_sort (catalogs, catalog_name_cmp); + + /* Generate dependency graph edges */ + for (l = catalogs; l; l = g_list_next (l)) + { + GladeCatalog *catalog = l->data, *dep; + + if (!catalog->dep_catalog) + continue; + + if ((dep = catalog_find_by_name ((loading) ? catalogs : loaded_catalogs, + catalog->dep_catalog))) + deps = _node_edge_prepend (deps, dep, catalog); + else + g_critical ("Catalog %s depends on catalog %s, not found", + catalog->name, catalog->dep_catalog); + } + + sorted = _glade_tsort (&catalogs, &deps); + + if (deps) + { + GList *l, *cycles = NULL; + + g_warning ("Circular dependency detected loading catalogs, they will be ignored"); + + for (l = deps; l; l = g_list_next (l)) + { + _NodeEdge *edge = l->data; + + g_message ("\t%s depends on %s", + GLADE_CATALOG (edge->successor)->name, + GLADE_CATALOG (edge->successor)->dep_catalog); + + if (loading && !g_list_find (cycles, edge->successor)) + cycles = g_list_prepend (cycles, edge->successor); + } + + if (cycles) + g_list_free_full (cycles, (GDestroyNotify) catalog_destroy); + + _node_edge_list_free (deps); + } + + return sorted; +} + +static GList * +catalogs_from_path (GList *catalogs, const gchar *path) +{ + GladeCatalog *catalog; + GDir *dir; + GError *error = NULL; + const gchar *filename; + + /* Silent return if the directory didn't exist */ + if (!g_file_test (path, G_FILE_TEST_IS_DIR)) + return catalogs; + + if ((dir = g_dir_open (path, 0, &error)) != NULL) + { + while ((filename = g_dir_read_name (dir))) + { + gchar *catalog_filename; + + if (!g_str_has_suffix (filename, ".xml")) + continue; + + /* Special case, ignore gresource files (which are present + * while running tests) + */ + if (g_str_has_suffix (filename, ".gresource.xml")) + continue; + + /* If we're running in the bundle, don't ever try to load + * anything except the GTK+ catalog + */ + if (g_getenv (GLADE_ENV_BUNDLED) != NULL && + strcmp (filename, "gtk+.xml") != 0) + continue; + + catalog_filename = g_build_filename (path, filename, NULL); + catalog = catalog_open (catalog_filename); + g_free (catalog_filename); + + if (catalog) + { + /* Verify that we are not loading the same catalog twice ! + */ + if (catalog_find_by_name (catalogs, catalog->name)) + catalog_destroy (catalog); + else + catalogs = g_list_prepend (catalogs, catalog); + } + else + g_warning ("Unable to open the catalog file %s.\n", filename); + } + + g_dir_close (dir); + } + else + g_warning ("Failed to open catalog directory '%s': %s", path, + error->message); + + + return catalogs; +} + +/** + * glade_catalog_add_path: + * @path: the new path containing catalogs + * + * Adds a new path to the list of path to look catalogs for. + */ +void +glade_catalog_add_path (const gchar *path) +{ + g_return_if_fail (path != NULL); + + if (g_list_find_custom (catalog_paths, path, (GCompareFunc) g_strcmp0) == NULL) + catalog_paths = g_list_append (catalog_paths, g_strdup (path)); +} + +/** + * glade_catalog_remove_path: + * @path: (nullable): the new path containing catalogs or %NULL to remove all of them + * + * Remove path from the list of path to look catalogs for. + * %NULL to remove all paths. + */ +void +glade_catalog_remove_path (const gchar *path) +{ + GList *l; + + if (path == NULL) + { + g_list_free_full (catalog_paths, g_free); + catalog_paths = NULL; + } + else if ((l = g_list_find_custom (catalog_paths, path, (GCompareFunc) g_strcmp0))) + { + catalog_paths = g_list_remove_link (catalog_paths, l); + } +} + +/** + * glade_catalog_get_extra_paths: + * + * Returns: (element-type utf8) (transfer none): a list paths added by glade_catalog_add_path() + */ +const GList * +glade_catalog_get_extra_paths (void) +{ + return catalog_paths; +} + +static void +adaptor_from_template (GladeCatalog *catalog, const gchar *filename) +{ + g_autofree gchar *tmpl_type = NULL, *tmpl_parent = NULL, *generic_name = NULL; + GladeWidgetAdaptor *adaptor; + GladeXmlNode *class_node; + + if (!g_str_has_suffix (filename, ".ui") && + !g_str_has_suffix (filename, ".glade")) + return; + + /* Load template and parse type and parent */ + if (!_glade_template_load (filename, &tmpl_type, &tmpl_parent)) + return; + + class_node = glade_xml_node_new (catalog->context, GLADE_TAG_GLADE_WIDGET_CLASS); + + /* Add data to class_node */ + generic_name = g_ascii_strdown (tmpl_type, -1); + glade_xml_node_set_property_string (class_node, GLADE_TAG_NAME, tmpl_type); + glade_xml_node_set_property_string (class_node, GLADE_XML_TAG_TEMPLATE, filename); + glade_xml_node_set_property_string (class_node, GLADE_TAG_TITLE, tmpl_type); + glade_xml_node_set_property_string (class_node, GLADE_TAG_GENERIC_NAME, generic_name); + + /* Load from fake catalog */ + if ((adaptor = glade_widget_adaptor_from_catalog (catalog, class_node, NULL))) + { + GladeWidgetGroup *group = catalog->widget_groups->data; + + /* Append adaptor to catalog */ + catalog->adaptors = g_list_prepend (catalog->adaptors, adaptor); + + /* And group */ + group->adaptors = g_list_prepend (group->adaptors, adaptor); + } + + glade_xml_node_delete (class_node); +} + +static void +on_templates_dir_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GladeCatalog *catalog) +{ + g_autofree gchar *file_path = g_file_get_path (file); + + if (event_type == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT) + { + adaptor_from_template (catalog, file_path); + } +} + +static void +load_templates_from_path (GladeCatalog *catalog, const gchar *path) +{ + GError *error = NULL; + GDir *dir; + + if (!g_file_test (path, G_FILE_TEST_IS_DIR)) + return; + + if ((dir = g_dir_open (path, 0, &error)) != NULL) + { + GFileMonitor *monitor = g_hash_table_lookup (catalog->monitors, path); + const gchar *filename; + + if (!monitor) + { + monitor = g_file_monitor_directory (g_file_new_for_path (path), + G_FILE_MONITOR_NONE, + NULL, + NULL); + g_signal_connect (monitor, "changed", + G_CALLBACK (on_templates_dir_changed), + catalog); + + g_hash_table_insert (catalog->monitors, g_strdup (path), monitor); + } + + while ((filename = g_dir_read_name (dir))) + { + g_autofree gchar *abs_filename = g_build_filename (path, filename, NULL); + adaptor_from_template (catalog, abs_filename); + } + + g_dir_close (dir); + } +} + +static GladeCatalog * +load_user_templates_catalog () +{ + GladeCatalog *catalog = glade_app_get_catalog ("user-templates"); + GList *l; + + /* Ensure user template catalog */ + if (!catalog) + { + GladeWidgetGroup *group = g_slice_new0 (GladeWidgetGroup); + + /* Create a runtime catalog for user templates */ + catalog = catalog_allocate (); + catalog->context = glade_xml_context_new (glade_xml_doc_new (), NULL); + catalog->name = g_strdup( "user-templates"); + catalog->monitors = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + g_object_unref); + + /* Use just one group for all the adaptors */ + group->name = g_strdup ("user-templates"); + group->title = g_strdup (_("User templates")); + group->expanded = FALSE; + + catalog->widget_groups = g_list_prepend (NULL, group); + } + + /* Load templates from extra catalog paths */ + for (l = catalog_paths; l; l = g_list_next (l)) + load_templates_from_path (catalog, l->data); + + /* Prepend instead of append to give priority over other catalogs */ + return catalog; +} + +/** + * glade_catalog_load_all: + * + * Loads all available catalogs in the system. + * First loads catalogs from GLADE_ENV_CATALOG_PATH, + * then from glade_app_get_catalogs_dir() and finally from paths specified with + * glade_catalog_add_path() + * + * Returns: (element-type GladeCatalog) (transfer none): the list of loaded GladeCatalog * + */ +const GList * +glade_catalog_load_all (void) +{ + GList *catalogs = NULL, *l, *adaptors; + GString *icon_warning = NULL; + const gchar *search_path; + + /* Make sure we don't init the catalogs twice */ + if (loaded_catalogs) + { + /* Reload user templates */ + load_user_templates_catalog (); + return loaded_catalogs; + } + + /* First load catalogs from user specified directories ... */ + if ((search_path = g_getenv (GLADE_ENV_CATALOG_PATH)) != NULL) + { + g_auto(GStrv) split; + + if ((split = g_strsplit (search_path, ":", 0)) != NULL) + { + gint i; + + for (i = 0; split[i] != NULL; i++) + catalogs = catalogs_from_path (catalogs, split[i]); + } + } + + /* ... Then load catalogs from standard install directory */ + if (g_getenv (GLADE_ENV_TESTING) == NULL) + catalogs = catalogs_from_path (catalogs, glade_app_get_catalogs_dir ()); + + /* And then load catalogs from extra paths */ + for (l = catalog_paths; l; l = g_list_next (l)) + catalogs = catalogs_from_path (catalogs, l->data); + + /* Catalogs need dependencies, most catalogs depend on + * the gtk+ catalog, but some custom toolkits may depend + * on the gnome catalog for instance. + */ + catalogs = glade_catalog_tsort (catalogs, TRUE); + + /* After sorting, execute init function and then load */ + for (l = catalogs; l; l = g_list_next (l)) + { + GladeCatalog *catalog = l->data; + + if (catalog->init_function) + catalog->init_function (catalog->name); + + catalog_load (catalog); + } + + /* Print a summery of widget adaptors missing icons. + */ + adaptors = glade_widget_adaptor_list_adaptors (); + for (l = adaptors; l; l = g_list_next (l)) + { + GladeWidgetAdaptor *adaptor = l->data; + + /* Dont print missing icons in unit tests */ + if (glade_widget_adaptor_get_missing_icon (adaptor) && + g_getenv (GLADE_ENV_TESTING) == NULL) + { + if (!icon_warning) + icon_warning = g_string_new ("Glade needs artwork; " + "a default icon will be used for " + "the following classes:"); + + g_string_append_printf (icon_warning, + "\n\t%s\tneeds an icon named '%s'", + glade_widget_adaptor_get_name (adaptor), + glade_widget_adaptor_get_missing_icon (adaptor)); + } + } + + g_list_free (adaptors); + + /* Load User defined templates */ + catalogs = g_list_prepend (catalogs, load_user_templates_catalog ()); + + if (icon_warning) + { + g_message ("%s", icon_warning->str); + g_string_free (icon_warning, TRUE); + } + + loaded_catalogs = catalogs; + + return loaded_catalogs; +} + +/** + * glade_catalog_get_name: + * @catalog: a catalog object + * + * Returns: The symbolic catalog name. + */ +const gchar * +glade_catalog_get_name (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->name; +} + +/** + * glade_catalog_get_prefix: + * @catalog: a catalog object + * + * Returns: The catalog path prefix. + */ +const gchar * +glade_catalog_get_prefix (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->prefix; +} + +/** + * glade_catalog_get_book: + * @catalog: a catalog object + * + * Returns: The Devhelp search domain. + */ +const gchar * +glade_catalog_get_book (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->book; +} + +/** + * glade_catalog_get_domain: + * @catalog: a catalog object + * + * Returns: The domain to be used to translate strings from this catalog + */ +const gchar * +glade_catalog_get_domain (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->domain; +} + +/** + * glade_catalog_get_icon_prefix: + * @catalog: a catalog object + * + * Returns: The prefix for icons. + */ +const gchar * +glade_catalog_get_icon_prefix (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->icon_prefix; +} + +/** + * glade_catalog_get_major_version: + * @catalog: a catalog object + * + * Returns: The catalog version + */ +guint16 +glade_catalog_get_major_version (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), 0); + + return catalog->major_version; +} + +/** + * glade_catalog_get_minor_version: + * @catalog: a catalog object + * + * Returns: The catalog minor version + */ +guint16 +glade_catalog_get_minor_version (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), 0); + + return catalog->minor_version; +} + +/** + * glade_catalog_get_targets: + * @catalog: a catalog object + * + * Returns: (transfer none) (element-type GladeTargetableVersion): the list of suitable version targets. + */ +GList * +glade_catalog_get_targets (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->targetable_versions; +} + +/** + * glade_catalog_get_widget_groups: + * @catalog: a catalog object + * + * Returns: (transfer none) (element-type GladeWidgetGroup): the list of widget groups (palette) + */ +GList * +glade_catalog_get_widget_groups (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->widget_groups; +} + +/** + * glade_catalog_get_adaptors: + * @catalog: a catalog object + * + * Returns: (transfer none) (element-type GladeWidgetAdaptor): the list of widget class adaptors + */ +GList * +glade_catalog_get_adaptors (GladeCatalog *catalog) +{ + g_return_val_if_fail (GLADE_IS_CATALOG (catalog), NULL); + + return catalog->adaptors; +} + +/** + * glade_catalog_is_loaded: + * @name: a catalog object + * + * Returns: Whether @name is loaded or not + */ +gboolean +glade_catalog_is_loaded (const gchar *name) +{ + g_return_val_if_fail (name != NULL, FALSE); + g_assert (loaded_catalogs != NULL); + return catalog_find_by_name (loaded_catalogs, name) != NULL; +} + +/** + * glade_catalog_destroy_all: + * + * Destroy and free all resources related with every loaded catalog. + */ +void +glade_catalog_destroy_all (void) +{ + /* close catalogs */ + if (loaded_catalogs) + { + GList *l; + for (l = loaded_catalogs; l; l = l->next) + catalog_destroy (GLADE_CATALOG (l->data)); + g_list_free (loaded_catalogs); + loaded_catalogs = NULL; + } + + /* close plugin modules */ + if (modules) + { + g_hash_table_destroy (modules); + modules = NULL; + } +} + +/** + * glade_widget_group_get_name: + * @group: a widget group + * + * Returns: the widget group name + */ +const gchar * +glade_widget_group_get_name (GladeWidgetGroup *group) +{ + g_return_val_if_fail (group != NULL, NULL); + + return group->name; +} + +/** + * glade_widget_group_get_title: + * @group: a widget group + * + * Returns: the widget group name used in the palette + */ +const gchar * +glade_widget_group_get_title (GladeWidgetGroup *group) +{ + g_return_val_if_fail (group != NULL, NULL); + + return group->title; +} + +/** + * glade_widget_group_get_expanded: + * @group: a widget group + * + * Returns: Whether group is expanded in the palette + */ +gboolean +glade_widget_group_get_expanded (GladeWidgetGroup *group) +{ + g_return_val_if_fail (group != NULL, FALSE); + + return group->expanded; +} + +/** + * glade_widget_group_get_adaptors: + * @group: a widget group + * + * Returns: (transfer none) (element-type GladeWidgetAdaptor): a list of class adaptors in the palette + */ +const GList * +glade_widget_group_get_adaptors (GladeWidgetGroup *group) +{ + g_return_val_if_fail (group != NULL, NULL); + + return group->adaptors; +} + +/* Private API */ + +GladeCatalog * +_glade_catalog_get_catalog (const gchar *name) +{ + g_return_val_if_fail (name != NULL, NULL); + g_assert (loaded_catalogs != NULL); + + return catalog_find_by_name (loaded_catalogs, name); +} + +GList * +_glade_catalog_tsort (GList *catalogs) +{ + return glade_catalog_tsort (catalogs, FALSE); +} diff --git a/gladeui/glade-catalog.h b/gladeui/glade-catalog.h new file mode 100644 index 0000000..5187e7d --- /dev/null +++ b/gladeui/glade-catalog.h @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2004 Imendio AB + * Copyright (C) 2007 The GNOME Foundation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __GLADE_CATALOG_H__ +#define __GLADE_CATALOG_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GLADE_CATALOG(c) ((GladeCatalog *) c) +#define GLADE_IS_CATALOG(c) (c != NULL) + + +#define GLADE_WIDGET_GROUP(g) ((GladeWidgetGroup *) g) +#define GLADE_IS_WIDGET_GROUP(g) (g != NULL) + +typedef struct _GladeCatalog GladeCatalog; +typedef struct _GladeWidgetGroup GladeWidgetGroup; + + +/** + * GladeCatalogInitFunc: + * @name: The name of the catalog + * + * Called once at glade startup time for every catalog, catalogs + * are initialized in order of dependencies. + */ +typedef void (*GladeCatalogInitFunc) (const gchar *name); + + +typedef struct { + gint major; + gint minor; +} GladeTargetableVersion; + + +void glade_catalog_add_path (const gchar *path); +void glade_catalog_remove_path (const gchar *path); +const GList *glade_catalog_get_extra_paths (void); +const GList *glade_catalog_load_all (void); + +const gchar *glade_catalog_get_name (GladeCatalog *catalog); +const gchar *glade_catalog_get_prefix (GladeCatalog *catalog); +const gchar *glade_catalog_get_icon_prefix(GladeCatalog *catalog); +const gchar *glade_catalog_get_domain (GladeCatalog *catalog); +const gchar *glade_catalog_get_book (GladeCatalog *catalog); + +GList *glade_catalog_get_targets (GladeCatalog *catalog); + +guint16 glade_catalog_get_major_version (GladeCatalog *catalog); +guint16 glade_catalog_get_minor_version (GladeCatalog *catalog); + +GList *glade_catalog_get_widget_groups (GladeCatalog *catalog); + +GList *glade_catalog_get_adaptors (GladeCatalog *catalog); + +gboolean glade_catalog_is_loaded (const gchar *name); + +void glade_catalog_destroy_all (void); + + +const gchar *glade_widget_group_get_name (GladeWidgetGroup *group); + +const gchar *glade_widget_group_get_title (GladeWidgetGroup *group); + +gboolean glade_widget_group_get_expanded (GladeWidgetGroup *group); + +const GList *glade_widget_group_get_adaptors (GladeWidgetGroup *group); + +G_END_DECLS + +#endif /* __GLADE_CATALOG_H__ */ diff --git a/gladeui/glade-cell-renderer-icon.c b/gladeui/glade-cell-renderer-icon.c new file mode 100644 index 0000000..e874bd8 --- /dev/null +++ b/gladeui/glade-cell-renderer-icon.c @@ -0,0 +1,239 @@ +/* + * Copyright (C) 2008 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#include "config.h" +#include "glade-cell-renderer-icon.h" +#include "glade-marshallers.h" + +static void glade_cell_renderer_icon_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec); +static void glade_cell_renderer_icon_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec); +static gboolean glade_cell_renderer_icon_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags); + +typedef struct _GladeCellRendererIconPrivate +{ + guint active : 1; + guint activatable : 1; +} GladeCellRendererIconPrivate; + +enum +{ + ACTIVATE, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_ACTIVATABLE, + PROP_ACTIVE, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES]; +static guint icon_cell_signals[LAST_SIGNAL] = { 0 }; + + +G_DEFINE_TYPE_WITH_PRIVATE (GladeCellRendererIcon, + glade_cell_renderer_icon, + GTK_TYPE_CELL_RENDERER_PIXBUF) + +static void glade_cell_renderer_icon_init (GladeCellRendererIcon *cellicon) +{ + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (cellicon); + + priv->activatable = TRUE; + priv->active = FALSE; + + g_object_set (G_OBJECT (cellicon), "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + NULL); +} + +static void +glade_cell_renderer_icon_class_init (GladeCellRendererIconClass *class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (class); + GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS (class); + + object_class->get_property = glade_cell_renderer_icon_get_property; + object_class->set_property = glade_cell_renderer_icon_set_property; + + cell_class->activate = glade_cell_renderer_icon_activate; + + properties[PROP_ACTIVE] = + g_param_spec_boolean ("active", "Icon state", + "The icon state of the button", + FALSE, + G_PARAM_READABLE | G_PARAM_WRITABLE); + + properties[PROP_ACTIVATABLE] = + g_param_spec_boolean ("activatable", + "Activatable", + "The icon button can be activated", + TRUE, + G_PARAM_READABLE | G_PARAM_WRITABLE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + icon_cell_signals[ACTIVATE] = + g_signal_new ("activate", + G_OBJECT_CLASS_TYPE (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeCellRendererIconClass, activate), + NULL, NULL, + _glade_marshal_VOID__STRING, G_TYPE_NONE, 1, G_TYPE_STRING); +} + +static void +glade_cell_renderer_icon_get_property (GObject *object, + guint param_id, + GValue *value, + GParamSpec *pspec) +{ + GladeCellRendererIcon *cellicon = GLADE_CELL_RENDERER_ICON (object); + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (cellicon); + + switch (param_id) + { + case PROP_ACTIVE: + g_value_set_boolean (value, priv->active); + break; + case PROP_ACTIVATABLE: + g_value_set_boolean (value, priv->activatable); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + + +static void +glade_cell_renderer_icon_set_property (GObject *object, + guint param_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeCellRendererIcon *cellicon = GLADE_CELL_RENDERER_ICON (object); + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (cellicon); + + switch (param_id) + { + case PROP_ACTIVE: + priv->active = g_value_get_boolean (value); + break; + case PROP_ACTIVATABLE: + priv->activatable = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec); + break; + } +} + +GtkCellRenderer * +glade_cell_renderer_icon_new (void) +{ + return g_object_new (GLADE_TYPE_CELL_RENDERER_ICON, NULL); +} + +static gint +glade_cell_renderer_icon_activate (GtkCellRenderer *cell, + GdkEvent *event, + GtkWidget *widget, + const gchar *path, + const GdkRectangle *background_area, + const GdkRectangle *cell_area, + GtkCellRendererState flags) +{ + GladeCellRendererIcon *cellicon = GLADE_CELL_RENDERER_ICON (cell); + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (cellicon); + + if (priv->activatable) + { + g_signal_emit (cell, icon_cell_signals[ACTIVATE], 0, path); + return TRUE; + } + + return FALSE; +} + +gboolean +glade_cell_renderer_icon_get_active (GladeCellRendererIcon *icon) +{ + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (icon); + + g_return_val_if_fail (GLADE_IS_CELL_RENDERER_ICON (icon), FALSE); + + return priv->active; +} + +void +glade_cell_renderer_icon_set_active (GladeCellRendererIcon *icon, + gboolean setting) +{ + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (icon); + + g_return_if_fail (GLADE_IS_CELL_RENDERER_ICON (icon)); + + if (priv->active != setting) + { + priv->active = setting ? TRUE : FALSE; + g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_ACTIVE]); + } +} + +gboolean +glade_cell_renderer_icon_get_activatable (GladeCellRendererIcon *icon) +{ + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (icon); + + g_return_val_if_fail (GLADE_IS_CELL_RENDERER_ICON (icon), FALSE); + + return priv->activatable; +} + +void +glade_cell_renderer_icon_set_activatable (GladeCellRendererIcon *icon, + gboolean setting) +{ + GladeCellRendererIconPrivate *priv = glade_cell_renderer_icon_get_instance_private (icon); + + g_return_if_fail (GLADE_IS_CELL_RENDERER_ICON (icon)); + + if (priv->activatable != setting) + { + priv->activatable = setting ? TRUE : FALSE; + g_object_notify_by_pspec (G_OBJECT (icon), properties[PROP_ACTIVATABLE]); + } +} diff --git a/gladeui/glade-cell-renderer-icon.h b/gladeui/glade-cell-renderer-icon.h new file mode 100644 index 0000000..e7fbad9 --- /dev/null +++ b/gladeui/glade-cell-renderer-icon.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2010 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#ifndef __GLADE_CELL_RENDERER_ICON_H__ +#define __GLADE_CELL_RENDERER_ICON_H__ + +#include + + +G_BEGIN_DECLS + +#define GLADE_TYPE_CELL_RENDERER_ICON glade_cell_renderer_icon_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeCellRendererIcon, glade_cell_renderer_icon, GLADE, CELL_RENDERER_ICON, GtkCellRendererPixbuf) + +struct _GladeCellRendererIconClass +{ + GtkCellRendererPixbufClass parent_class; + + void (* activate) (GladeCellRendererIcon *cell_renderer_icon, + const gchar *path); +}; + +GtkCellRenderer *glade_cell_renderer_icon_new (void); + +gboolean glade_cell_renderer_icon_get_active (GladeCellRendererIcon *icon); +void glade_cell_renderer_icon_set_active (GladeCellRendererIcon *icon, + gboolean setting); + +gboolean glade_cell_renderer_icon_get_activatable (GladeCellRendererIcon *icon); +void glade_cell_renderer_icon_set_activatable (GladeCellRendererIcon *icon, + gboolean setting); + + +G_END_DECLS + +#endif /* __GLADE_CELL_RENDERER_ICON_H__ */ diff --git a/gladeui/glade-clipboard.c b/gladeui/glade-clipboard.c new file mode 100644 index 0000000..f2784ff --- /dev/null +++ b/gladeui/glade-clipboard.c @@ -0,0 +1,224 @@ +/* + * glade-clipboard.c - An object for handling Cut/Copy/Paste. + * + * Copyright (C) 2001 The GNOME Foundation. + * + * Author(s): + * Archit Baweja + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, + * USA. + */ + +#include "config.h" + +/** + * SECTION:glade-clipboard + * @Short_Description: A list of #GladeWidget objects not in any #GladeProject. + * + * The #GladeClipboard is a singleton and is an accumulative shelf + * of all cut or copied #GladeWidget in the application. A #GladeWidget + * can be cut from one #GladeProject and pasted to another. + */ + +#include +#include "glade.h" +#include "glade-clipboard.h" +#include "glade-widget.h" +#include "glade-placeholder.h" +#include "glade-project.h" + +typedef struct _GladeClipboardPrivate +{ + GList *widgets; /* A list of GladeWidget's on the clipboard */ + gboolean has_selection; /* TRUE if clipboard has selection */ +} GladeClipboardPrivate; + +enum +{ + PROP_0, + PROP_HAS_SELECTION, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES]; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeClipboard, glade_clipboard, G_TYPE_OBJECT) + +static void +glade_project_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeClipboard *clipboard = GLADE_CLIPBOARD (object); + GladeClipboardPrivate *priv = glade_clipboard_get_instance_private (clipboard); + + switch (prop_id) + { + case PROP_HAS_SELECTION: + g_value_set_boolean (value, priv->has_selection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_clipboard_class_init (GladeClipboardClass * klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = glade_project_get_property; + + properties[PROP_HAS_SELECTION] = + g_param_spec_boolean ("has-selection", + "Has Selection", + "Whether clipboard has a selection of items to paste", + FALSE, + G_PARAM_READABLE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + +static void +glade_clipboard_init (GladeClipboard *clipboard) +{ + GladeClipboardPrivate *priv = glade_clipboard_get_instance_private (clipboard); + + priv->widgets = NULL; + priv->has_selection = FALSE; +} + +static void +glade_clipboard_set_has_selection (GladeClipboard *clipboard, + gboolean has_selection) +{ + GladeClipboardPrivate *priv = glade_clipboard_get_instance_private (clipboard); + + if (priv->has_selection != has_selection) + { + priv->has_selection = has_selection; + g_object_notify_by_pspec (G_OBJECT (clipboard), properties[PROP_HAS_SELECTION]); + } + +} + +/** + * glade_clipboard_get_has_selection: + * @clipboard: a #GladeClipboard + * + * Returns: TRUE if this clipboard has selected items to paste. + */ +gboolean +glade_clipboard_get_has_selection (GladeClipboard *clipboard) +{ + GladeClipboardPrivate *priv = glade_clipboard_get_instance_private (clipboard); + + g_return_val_if_fail (GLADE_IS_CLIPBOARD (clipboard), FALSE); + + return priv->has_selection; +} + +/** + * glade_clipboard_widgets: + * @clipboard: a #GladeClipboard + * + * Returns: (element-type GladeWidget) (transfer none): a #GList of #GladeWidgets + */ +GList * +glade_clipboard_widgets (GladeClipboard *clipboard) +{ + GladeClipboardPrivate *priv = glade_clipboard_get_instance_private (clipboard); + + g_return_val_if_fail (GLADE_IS_CLIPBOARD (clipboard), NULL); + + return priv->widgets; +} + +/** + * glade_clipboard_new: + * + * Returns: a new #GladeClipboard object + */ +GladeClipboard * +glade_clipboard_new (void) +{ + return GLADE_CLIPBOARD (g_object_new (GLADE_TYPE_CLIPBOARD, NULL)); +} + +/** + * glade_clipboard_add: + * @clipboard: a #GladeClipboard + * @widgets: (element-type GladeWidget): a #GList of #GladeWidgets + * + * Adds @widgets to @clipboard. + * This increases the reference count of each #GladeWidget in @widgets. + */ +void +glade_clipboard_add (GladeClipboard *clipboard, GList *widgets) +{ + GladeClipboardPrivate *priv = glade_clipboard_get_instance_private (clipboard); + GladeWidget *widget; + GList *list; + + g_return_if_fail (GLADE_IS_CLIPBOARD (clipboard)); + + glade_clipboard_clear (clipboard); + + /* + * Add the widgets to the list of children. + */ + for (list = widgets; list && list->data; list = list->next) + { + widget = list->data; + priv->widgets = + g_list_prepend (priv->widgets, g_object_ref_sink (G_OBJECT (widget))); + } + + glade_clipboard_set_has_selection (clipboard, TRUE); +} + +/** + * glade_clipboard_clear: + * @clipboard: a #GladeClipboard + * + * Removes all widgets from the @clipboard. + */ +void +glade_clipboard_clear (GladeClipboard *clipboard) +{ + GladeClipboardPrivate *priv = glade_clipboard_get_instance_private (clipboard); + GladeWidget *widget; + GList *list; + + g_return_if_fail (GLADE_IS_CLIPBOARD (clipboard)); + + for (list = priv->widgets; list && list->data; list = list->next) + { + widget = list->data; + + g_object_unref (G_OBJECT (widget)); + } + + priv->widgets = + (g_list_free (priv->widgets), NULL); + + glade_clipboard_set_has_selection (clipboard, FALSE); +} diff --git a/gladeui/glade-clipboard.h b/gladeui/glade-clipboard.h new file mode 100644 index 0000000..e61712f --- /dev/null +++ b/gladeui/glade-clipboard.h @@ -0,0 +1,28 @@ +#ifndef __GLADE_CLIPBOARD_H__ +#define __GLADE_CLIPBOARD_H__ + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_CLIPBOARD glade_clipboard_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeClipboard, glade_clipboard, GLADE, CLIPBOARD, GObject) + +struct _GladeClipboardClass +{ + GObjectClass parent_class; + + gpointer padding[4]; +}; + +GladeClipboard *glade_clipboard_new (void); +void glade_clipboard_add (GladeClipboard *clipboard, + GList *widgets); +void glade_clipboard_clear (GladeClipboard *clipboard); + +gboolean glade_clipboard_get_has_selection(GladeClipboard *clipboard); +GList *glade_clipboard_widgets (GladeClipboard *clipboard); + +G_END_DECLS + +#endif /* __GLADE_CLIPBOARD_H__ */ diff --git a/gladeui/glade-command.c b/gladeui/glade-command.c new file mode 100644 index 0000000..eaec608 --- /dev/null +++ b/gladeui/glade-command.c @@ -0,0 +1,3033 @@ +/* + * Copyright (C) 2002 Joaquín Cuenca Abela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Joaquín Cuenca Abela + * Archit Baweja + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +/** + * SECTION:glade-command + * @Short_Description: An event filter to implement the Undo/Redo stack. + * + * The Glade Command api allows us to view user actions as items and execute + * and undo those items; each #GladeProject has its own Undo/Redo stack. + */ + +#include +#include +#include +#include "glade.h" +#include "glade-project.h" +#include "glade-xml-utils.h" +#include "glade-widget.h" +#include "glade-palette.h" +#include "glade-command.h" +#include "glade-property.h" +#include "glade-property-def.h" +#include "glade-debug.h" +#include "glade-placeholder.h" +#include "glade-clipboard.h" +#include "glade-signal.h" +#include "glade-app.h" +#include "glade-name-context.h" + +typedef struct _GladeCommandPrivate +{ + GladeProject *project; /* The project this command is created for */ + + gchar *description; /* a string describing the command. + * It's used in the undo/redo menu entry. + */ + + gint group_id; /* If this is part of a command group, this is + * the group id (id is needed only to ensure that + * consecutive groups dont get merged). + */ +} GladeCommandPrivate; + +/* Concerning placeholders: we do not hold any reference to placeholders, + * placeholders that are supplied by the backend are not reffed, placeholders + * that are created by glade-command are temporarily owned by glade-command + * untill they are added to a container; in which case it belongs to GTK+ + * and the backend (but I prefer to think of it as the backend). + */ +typedef struct +{ + GladeWidget *widget; + GladeWidget *parent; + GList *reffed; + GladePlaceholder *placeholder; + gboolean props_recorded; + GList *pack_props; + gchar *special_type; + gulong handler_id; +} CommandData; + +/* Group description used for the current group + */ +static gchar *gc_group_description = NULL; + +/* Use an id to avoid grouping together consecutive groups + */ +static gint gc_group_id = 1; + +/* Groups can be grouped together, push/pop must balance (duh!) + */ +static gint gc_group_depth = 0; + + +G_DEFINE_TYPE_WITH_PRIVATE (GladeCommand, glade_command, G_TYPE_OBJECT) + +static void +glade_command_finalize (GObject *obj) +{ + GladeCommand *cmd = (GladeCommand *) obj; + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + + g_clear_pointer (&priv->description, g_free); + + G_OBJECT_CLASS (glade_command_parent_class)->finalize (obj); +} + +static gboolean +glade_command_unifies_impl (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + return FALSE; +} + +static void +glade_command_collapse_impl (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + g_return_if_reached (); +} + +static void +glade_command_init (GladeCommand *command) +{ +} + +static void +glade_command_class_init (GladeCommandClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = glade_command_finalize; + + klass->undo = NULL; + klass->execute = NULL; + klass->unifies = glade_command_unifies_impl; + klass->collapse = glade_command_collapse_impl; +} + +/* Macros for defining the derived command types */ +#define GLADE_MAKE_COMMAND(type, func, upper_type) \ +G_DECLARE_FINAL_TYPE (type, func, GLADE, upper_type, GladeCommand) \ +G_DEFINE_TYPE (type, func, GLADE_TYPE_COMMAND) \ +static gboolean \ +func ## _undo (GladeCommand *me); \ +static gboolean \ +func ## _execute (GladeCommand *me); \ +static void \ +func ## _finalize (GObject *object); \ +static gboolean \ +func ## _unifies (GladeCommand *this_cmd, GladeCommand *other_cmd); \ +static void \ +func ## _collapse (GladeCommand *this_cmd, GladeCommand *other_cmd); \ +static void \ +func ## _class_init (type ## Class *klass) \ +{ \ + GladeCommandClass *command_class = GLADE_COMMAND_CLASS (klass); \ + GObjectClass* object_class = G_OBJECT_CLASS (klass); \ + command_class->undo = func ## _undo; \ + command_class->execute = func ## _execute; \ + command_class->unifies = func ## _unifies; \ + command_class->collapse = func ## _collapse; \ + object_class->finalize = func ## _finalize; \ +} \ +static void \ +func ## _init (type *self) \ +{ \ +} + + +const gchar * +glade_command_description (GladeCommand *command) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (command); + + g_return_val_if_fail (GLADE_IS_COMMAND (command), NULL); + + return priv->description; +} + +gint +glade_command_group_id (GladeCommand *command) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (command); + + g_return_val_if_fail (GLADE_IS_COMMAND (command), -1); + + return priv->group_id; +} + + +/** + * glade_command_execute: + * @command: A #GladeCommand + * + * Executes @command + * + * Returns: whether the command was successfully executed + */ +gboolean +glade_command_execute (GladeCommand *command) +{ + g_return_val_if_fail (GLADE_IS_COMMAND (command), FALSE); + return GLADE_COMMAND_GET_CLASS (command)->execute (command); +} + + +/** + * glade_command_undo: + * @command: A #GladeCommand + * + * Undo the effects of @command + * + * Returns: whether the command was successfully reversed + */ +gboolean +glade_command_undo (GladeCommand *command) +{ + g_return_val_if_fail (GLADE_IS_COMMAND (command), FALSE); + return GLADE_COMMAND_GET_CLASS (command)->undo (command); +} + +/** + * glade_command_unifies: + * @command: A #GladeCommand + * @other: another #GladeCommand + * + * Checks whether @command and @other can be unified + * to make one single command. + * + * Returns: whether they can be unified. + */ +gboolean +glade_command_unifies (GladeCommand *command, GladeCommand *other) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (command); + GladeCommandPrivate *other_priv = glade_command_get_instance_private (other); + + g_return_val_if_fail (command, FALSE); + + /* Cannot unify with a part of a command group. + * Unify atomic commands only + */ + if (priv->group_id != 0 || (other && other_priv->group_id != 0)) + return FALSE; + + return GLADE_COMMAND_GET_CLASS (command)->unifies (command, other); +} + +/** + * glade_command_collapse: + * @command: A #GladeCommand + * @other: another #GladeCommand + * + * Merges @other into @command, so that @command now + * covers both commands and @other can be dispensed with. + */ +void +glade_command_collapse (GladeCommand *command, GladeCommand *other) +{ + g_return_if_fail (command); + GLADE_COMMAND_GET_CLASS (command)->collapse (command, other); +} + +/** + * glade_command_push_group: + * @fmt: The collective description of the command group. + * only the description of the first group on the + * stack is used when embedding groups. + * @...: args to the format string. + * + * Marks the beginning of a group. + */ +void +glade_command_push_group (const gchar *fmt, ...) +{ + va_list args; + + g_return_if_fail (fmt); + + /* Only use the description for the first group. + */ + if (gc_group_depth++ == 0) + { + va_start (args, fmt); + gc_group_description = g_strdup_vprintf (fmt, args); + va_end (args); + } +} + +/** + * glade_command_pop_group: + * + * Mark the end of a command group. + */ +void +glade_command_pop_group (void) +{ + if (gc_group_depth-- == 1) + { + gc_group_description = (g_free (gc_group_description), NULL); + gc_group_id++; + } + + if (gc_group_depth < 0) + g_critical ("Unbalanced group stack detected in %s\n", G_STRFUNC); +} + +gint +glade_command_get_group_depth (void) +{ + return gc_group_depth; +} + +static void +glade_command_check_group (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + + g_return_if_fail (GLADE_IS_COMMAND (cmd)); + + if (gc_group_description) + { + priv->description = + (g_free (priv->description), g_strdup (gc_group_description)); + priv->group_id = gc_group_id; + } +} + + +/****************************************************/ +/******* GLADE_COMMAND_PROPERTY_ENABLED *******/ +/****************************************************/ +struct _GladeCommandPropertyEnabled +{ + GladeCommand parent; + + GladeProperty *property; + gboolean old_enabled; + gboolean new_enabled; +}; + +/* standard macros */ +#define GLADE_TYPE_COMMAND_PROPERTY_ENABLED glade_command_property_enabled_get_type () +GLADE_MAKE_COMMAND (GladeCommandPropertyEnabled, glade_command_property_enabled, COMMAND_PROPERTY_ENABLED) + +static gboolean +glade_command_property_enabled_execute (GladeCommand *cmd) +{ + GladeCommandPropertyEnabled *me = GLADE_COMMAND_PROPERTY_ENABLED (cmd); + + glade_property_set_enabled (me->property, me->new_enabled); + + return TRUE; +} + +static gboolean +glade_command_property_enabled_undo (GladeCommand *cmd) +{ + GladeCommandPropertyEnabled *me = GLADE_COMMAND_PROPERTY_ENABLED (cmd); + + glade_property_set_enabled (me->property, me->old_enabled); + + return TRUE; +} + +static void +glade_command_property_enabled_finalize (GObject *obj) +{ + GladeCommandPropertyEnabled *me; + + g_return_if_fail (GLADE_IS_COMMAND_PROPERTY_ENABLED (obj)); + + me = GLADE_COMMAND_PROPERTY_ENABLED (obj); + + g_clear_object (&me->property); + glade_command_finalize (obj); +} + +static gboolean +glade_command_property_enabled_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + GladeCommandPropertyEnabled *cmd1; + GladeCommandPropertyEnabled *cmd2; + + if (!other_cmd) + { + if (GLADE_IS_COMMAND_PROPERTY_ENABLED (this_cmd)) + { + cmd1 = (GladeCommandPropertyEnabled *) this_cmd; + + return (cmd1->old_enabled == cmd1->new_enabled); + } + return FALSE; + } + + if (GLADE_IS_COMMAND_PROPERTY_ENABLED (this_cmd) && + GLADE_IS_COMMAND_PROPERTY_ENABLED (other_cmd)) + { + cmd1 = GLADE_COMMAND_PROPERTY_ENABLED (this_cmd); + cmd2 = GLADE_COMMAND_PROPERTY_ENABLED (other_cmd); + + return (cmd1->property == cmd2->property); + } + + return FALSE; +} + +static void +glade_command_property_enabled_collapse (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + GladeCommandPropertyEnabled *this = GLADE_COMMAND_PROPERTY_ENABLED (this_cmd); + GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd); + GladeCommandPropertyEnabled *other = GLADE_COMMAND_PROPERTY_ENABLED (other_cmd); + GladeWidget *widget; + GladePropertyDef *pdef; + + this->new_enabled = other->new_enabled; + + widget = glade_property_get_widget (this->property); + pdef = glade_property_get_def (this->property); + + g_free (this_priv->description); + if (this->new_enabled) + this_priv->description = + g_strdup_printf (_("Enabling property %s on widget %s"), + glade_property_def_get_name (pdef), + glade_widget_get_name (widget)); + else + this_priv->description = + g_strdup_printf (_("Disabling property %s on widget %s"), + glade_property_def_get_name (pdef), + glade_widget_get_name (widget)); +} + +/** + * glade_command_set_property_enabled: + * @property: An optional #GladeProperty + * @enabled: Whether the property should be enabled + * + * Enables or disables @property. + * + * @property must be an optional property. + */ +void +glade_command_set_property_enabled (GladeProperty *property, + gboolean enabled) +{ + GladeCommandPropertyEnabled *me; + GladeCommand *cmd; + GladeCommandPrivate *cmd_priv; + GladeWidget *widget; + GladePropertyDef *pdef; + gboolean old_enabled; + + /* Sanity checks */ + g_return_if_fail (GLADE_IS_PROPERTY (property)); + + widget = glade_property_get_widget (property); + g_return_if_fail (GLADE_IS_WIDGET (widget)); + + /* Only applies to optional properties */ + pdef = glade_property_get_def (property); + g_return_if_fail (glade_property_def_optional (pdef)); + + /* Fetch current state */ + old_enabled = glade_property_get_enabled (property); + + /* Avoid useless command */ + if (old_enabled == enabled) + return; + + me = g_object_new (GLADE_TYPE_COMMAND_PROPERTY_ENABLED, NULL); + cmd = GLADE_COMMAND (me); + cmd_priv = glade_command_get_instance_private (cmd); + cmd_priv->project = glade_widget_get_project (widget); + + me->property = g_object_ref (property); + me->new_enabled = enabled; + me->old_enabled = old_enabled; + + if (enabled) + cmd_priv->description = + g_strdup_printf (_("Enabling property %s on widget %s"), + glade_property_def_get_name (pdef), + glade_widget_get_name (widget)); + else + cmd_priv->description = + g_strdup_printf (_("Disabling property %s on widget %s"), + glade_property_def_get_name (pdef), + glade_widget_get_name (widget)); + + glade_command_check_group (GLADE_COMMAND (me)); + + if (glade_command_property_enabled_execute (cmd)) + glade_project_push_undo (cmd_priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); +} + +/**************************************************/ +/******* GLADE_COMMAND_SET_PROPERTY *******/ +/**************************************************/ + +/* create a new GladeCommandSetProperty class. Objects of this class will + * encapsulate a "set property" operation */ + +struct _GladeCommandSetProperty +{ + GladeCommand parent; + + gboolean set_once; + gboolean undo; + GList *sdata; +}; + +/* standard macros */ +#define GLADE_TYPE_COMMAND_SET_PROPERTY glade_command_set_property_get_type () +GLADE_MAKE_COMMAND (GladeCommandSetProperty, glade_command_set_property, COMMAND_SET_PROPERTY) + +/* Undo the last "set property command" */ +static gboolean +glade_command_set_property_undo (GladeCommand *cmd) +{ + return glade_command_set_property_execute (cmd); +} + +/* + * Execute the set property command and revert it. IE, after the execution of + * this function cmd will point to the undo action + */ +static gboolean +glade_command_set_property_execute (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + GladeCommandSetProperty *me = (GladeCommandSetProperty *) cmd; + GList *l; + gboolean success; + gboolean retval = FALSE; + + g_return_val_if_fail (me != NULL, FALSE); + + if (me->set_once != FALSE) + glade_property_push_superuser (); + + for (l = me->sdata; l; l = l->next) + { + GValue new_value = { 0, }; + GladeCommandSetPropData *sdata = l->data; + GladePropertyDef *pdef = glade_property_get_def (sdata->property); + GladeWidget *widget = glade_property_get_widget (sdata->property); + + g_value_init (&new_value, G_VALUE_TYPE (sdata->new_value)); + + if (me->undo) + g_value_copy (sdata->old_value, &new_value); + else + g_value_copy (sdata->new_value, &new_value); + +#ifdef GLADE_ENABLE_DEBUG + if (glade_get_debug_flags () & GLADE_DEBUG_COMMANDS) + { + gchar *str = + glade_widget_adaptor_string_from_value + (glade_property_def_get_adaptor (pdef), pdef, &new_value); + + g_print ("Setting %s property of %s to %s (sumode: %d)\n", + glade_property_def_id (pdef), + glade_widget_get_name (widget), + str, glade_property_superuser ()); + + g_free (str); + } +#endif + + /* Packing properties need to be refreshed here since + * they are reset when they get added to containers. + */ + if (glade_property_def_get_is_packing (pdef)) + { + GladeProperty *tmp_prop; + + tmp_prop = glade_widget_get_pack_property (widget, glade_property_def_id (pdef)); + + if (sdata->property != tmp_prop) + { + g_object_unref (sdata->property); + sdata->property = g_object_ref (tmp_prop); + } + } + + /* Make sure the target object has a name for object properties */ + if (glade_property_def_is_object (pdef)) + { + GObject *pobject = g_value_get_object (&new_value); + GladeWidget *pwidget; + + if (pobject && (pwidget = glade_widget_get_from_gobject (pobject))) + glade_widget_ensure_name (pwidget, priv->project, TRUE); + } + + success = glade_property_set_value (sdata->property, &new_value); + retval = retval || success; + + if (!me->set_once && success) + { + /* If some verify functions didnt pass on + * the first go.. we need to record the actual + * properties here. XXX should be able to use glade_property_get_value() here + */ + g_value_copy (glade_property_inline_value (sdata->property), sdata->new_value); + } + + g_value_unset (&new_value); + } + + if (me->set_once != FALSE) + glade_property_pop_superuser (); + + me->set_once = TRUE; + me->undo = !me->undo; + + return retval; +} + +static void +glade_command_set_property_finalize (GObject *obj) +{ + GladeCommandSetProperty *me; + GList *l; + + me = GLADE_COMMAND_SET_PROPERTY (obj); + + for (l = me->sdata; l; l = l->next) + { + GladeCommandSetPropData *sdata = l->data; + + if (sdata->property) + g_object_unref (G_OBJECT (sdata->property)); + + if (sdata->old_value) + { + if (G_VALUE_TYPE (sdata->old_value) != 0) + g_value_unset (sdata->old_value); + g_free (sdata->old_value); + } + if (G_VALUE_TYPE (sdata->new_value) != 0) + g_value_unset (sdata->new_value); + g_free (sdata->new_value); + + } + glade_command_finalize (obj); +} + +static gboolean +glade_command_set_property_unifies (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + GladeCommandSetProperty *cmd1, *cmd2; + GladePropertyDef *pdef1, *pdef2; + GladeCommandSetPropData *pdata1, *pdata2; + GladeWidget *widget1, *widget2; + GList *list, *l; + + if (!other_cmd) + { + if (GLADE_IS_COMMAND_SET_PROPERTY (this_cmd)) + { + cmd1 = (GladeCommandSetProperty *) this_cmd; + + for (list = cmd1->sdata; list; list = list->next) + { + pdata1 = list->data; + pdef1 = glade_property_get_def (pdata1->property); + + if (glade_property_def_compare (pdef1, + pdata1->old_value, + pdata1->new_value)) + return FALSE; + } + return TRUE; + + } + return FALSE; + } + + if (GLADE_IS_COMMAND_SET_PROPERTY (this_cmd) && + GLADE_IS_COMMAND_SET_PROPERTY (other_cmd)) + { + cmd1 = (GladeCommandSetProperty *) this_cmd; + cmd2 = (GladeCommandSetProperty *) other_cmd; + + if (g_list_length (cmd1->sdata) != g_list_length (cmd2->sdata)) + return FALSE; + + for (list = cmd1->sdata; list; list = list->next) + { + pdata1 = list->data; + pdef1 = glade_property_get_def (pdata1->property); + widget1 = glade_property_get_widget (pdata1->property); + + for (l = cmd2->sdata; l; l = l->next) + { + pdata2 = l->data; + pdef2 = glade_property_get_def (pdata2->property); + widget2 = glade_property_get_widget (pdata2->property); + + if (widget1 == widget2 && + glade_property_def_match (pdef1, pdef2)) + break; + } + + /* If both lists are the same length, and one class type + * is not found in the other list, then we cant unify. + */ + if (l == NULL) + return FALSE; + } + + return TRUE; + } + return FALSE; +} + +static void +glade_command_set_property_collapse (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + GladeCommandSetProperty *cmd1, *cmd2; + GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd); + GladeCommandPrivate *other_priv = glade_command_get_instance_private (other_cmd); + GladeCommandSetPropData *pdata1, *pdata2; + GladePropertyDef *pdef1, *pdef2; + GList *list, *l; + + g_return_if_fail (GLADE_IS_COMMAND_SET_PROPERTY (this_cmd) && + GLADE_IS_COMMAND_SET_PROPERTY (other_cmd)); + + cmd1 = (GladeCommandSetProperty *) this_cmd; + cmd2 = (GladeCommandSetProperty *) other_cmd; + + + for (list = cmd1->sdata; list; list = list->next) + { + pdata1 = list->data; + pdef1 = glade_property_get_def (pdata1->property); + + for (l = cmd2->sdata; l; l = l->next) + { + pdata2 = l->data; + pdef2 = glade_property_get_def (pdata2->property); + + if (glade_property_def_match (pdef1, pdef2)) + { + /* Merge the GladeCommandSetPropData structs manually here + */ + g_value_copy (pdata2->new_value, pdata1->new_value); + break; + } + } + } + + /* Set the description + */ + g_free (this_priv->description); + this_priv->description = other_priv->description; + other_priv->description = NULL; +} + + +#define MAX_UNDO_MENU_ITEM_VALUE_LEN 10 +static gchar * +glade_command_set_property_description (GladeCommandSetProperty *me) +{ + GladeCommandSetPropData *sdata; + gchar *description = NULL; + gchar *value_name; + GladePropertyDef *pdef; + GladeWidget *widget; + + g_assert (me->sdata); + + if (g_list_length (me->sdata) > 1) + description = g_strdup_printf (_("Setting multiple properties")); + else + { + sdata = me->sdata->data; + pdef = glade_property_get_def (sdata->property); + widget = glade_property_get_widget (sdata->property); + value_name = glade_widget_adaptor_string_from_value + (glade_property_def_get_adaptor (pdef), pdef, sdata->new_value); + + if (!value_name || strlen (value_name) > MAX_UNDO_MENU_ITEM_VALUE_LEN + || strchr (value_name, '_')) + { + description = g_strdup_printf (_("Setting %s of %s"), + glade_property_def_get_name (pdef), + glade_widget_get_name (widget)); + } + else + { + description = g_strdup_printf (_("Setting %s of %s to %s"), + glade_property_def_get_name (pdef), + glade_widget_get_name (widget), + value_name); + } + g_free (value_name); + } + + return description; +} + +/** + * glade_command_set_properties_list: + * @project: a #GladeProject + * @props: (element-type GladeProperty): List of #GladeProperty + */ +void +glade_command_set_properties_list (GladeProject *project, GList *props) +{ + GladeCommandSetProperty *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + GladeCommandSetPropData *sdata; + GList *list; + gboolean success; + gboolean multiple; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (props); + + me = (GladeCommandSetProperty *) + g_object_new (GLADE_TYPE_COMMAND_SET_PROPERTY, NULL); + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + priv->project = project; + + /* Ref all props */ + for (list = props; list; list = list->next) + { + sdata = list->data; + g_object_ref (G_OBJECT (sdata->property)); + } + + me->sdata = props; + priv->description = glade_command_set_property_description (me); + + multiple = g_list_length (me->sdata) > 1; + if (multiple) + glade_command_push_group ("%s", priv->description); + + glade_command_check_group (cmd); + + /* Push onto undo stack only if it executes successfully. */ + success = glade_command_set_property_execute (cmd); + + if (success) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); + + if (multiple) + glade_command_pop_group (); +} + + +void +glade_command_set_properties (GladeProperty *property, + const GValue *old_value, + const GValue *new_value, + ...) +{ + GladeCommandSetPropData *sdata; + GladeProperty *prop; + GladeWidget *widget; + GladeProject *project; + GValue *ovalue, *nvalue; + GList *list = NULL; + va_list vl; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + + /* Add first set */ + sdata = g_new (GladeCommandSetPropData, 1); + sdata->property = property; + sdata->old_value = g_new0 (GValue, 1); + sdata->new_value = g_new0 (GValue, 1); + g_value_init (sdata->old_value, G_VALUE_TYPE (old_value)); + g_value_init (sdata->new_value, G_VALUE_TYPE (new_value)); + g_value_copy (old_value, sdata->old_value); + g_value_copy (new_value, sdata->new_value); + list = g_list_prepend (list, sdata); + + va_start (vl, new_value); + while ((prop = va_arg (vl, GladeProperty *)) != NULL) + { + ovalue = va_arg (vl, GValue *); + nvalue = va_arg (vl, GValue *); + + g_assert (GLADE_IS_PROPERTY (prop)); + g_assert (G_IS_VALUE (ovalue)); + g_assert (G_IS_VALUE (nvalue)); + + sdata = g_new (GladeCommandSetPropData, 1); + sdata->property = g_object_ref (GLADE_PROPERTY (prop)); + sdata->old_value = g_new0 (GValue, 1); + sdata->new_value = g_new0 (GValue, 1); + g_value_init (sdata->old_value, G_VALUE_TYPE (ovalue)); + g_value_init (sdata->new_value, G_VALUE_TYPE (nvalue)); + g_value_copy (ovalue, sdata->old_value); + g_value_copy (nvalue, sdata->new_value); + list = g_list_prepend (list, sdata); + } + va_end (vl); + + widget = glade_property_get_widget (property); + project = glade_widget_get_project (widget); + glade_command_set_properties_list (project, list); +} + +void +glade_command_set_property_value (GladeProperty *property, const GValue *pvalue) +{ + + /* Dont generate undo/redo items for unchanging property values. + */ + if (glade_property_equals_value (property, pvalue)) + return; + + glade_command_set_properties (property, glade_property_inline_value (property), pvalue, NULL); +} + +void +glade_command_set_property (GladeProperty * property, ...) +{ + GValue *value; + va_list args; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + + va_start (args, property); + value = glade_property_def_make_gvalue_from_vl (glade_property_get_def (property), args); + va_end (args); + + glade_command_set_property_value (property, value); +} + +/**************************************************/ +/******* GLADE_COMMAND_SET_NAME *******/ +/**************************************************/ + +/* create a new GladeCommandSetName class. Objects of this class will + * encapsulate a "set name" operation */ +struct _GladeCommandSetName +{ + GladeCommand parent; + + GladeWidget *widget; + gchar *old_name; + gchar *name; +}; + +/* standard macros */ +#define GLADE_TYPE_COMMAND_SET_NAME glade_command_set_name_get_type () +GLADE_MAKE_COMMAND (GladeCommandSetName, glade_command_set_name, COMMAND_SET_NAME) + +/* Undo the last "set name command" */ +static gboolean +glade_command_set_name_undo (GladeCommand *cmd) +{ + return glade_command_set_name_execute (cmd); +} + +/* + * Execute the set name command and revert it. Ie, after the execution of this + * function cmd will point to the undo action + */ +static gboolean +glade_command_set_name_execute (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + GladeCommandSetName *me = GLADE_COMMAND_SET_NAME (cmd); + char *tmp; + + g_return_val_if_fail (me != NULL, TRUE); + g_return_val_if_fail (me->widget != NULL, TRUE); + g_return_val_if_fail (me->name != NULL, TRUE); + + glade_project_set_widget_name (priv->project, me->widget, me->name); + + tmp = me->old_name; + me->old_name = me->name; + me->name = tmp; + + return TRUE; +} + +static void +glade_command_set_name_finalize (GObject *obj) +{ + GladeCommandSetName *me; + + g_return_if_fail (GLADE_IS_COMMAND_SET_NAME (obj)); + + me = GLADE_COMMAND_SET_NAME (obj); + + g_free (me->old_name); + g_free (me->name); + + glade_command_finalize (obj); +} + +static gboolean +glade_command_set_name_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + GladeCommandSetName *cmd1; + GladeCommandSetName *cmd2; + + if (!other_cmd) + { + if (GLADE_IS_COMMAND_SET_NAME (this_cmd)) + { + cmd1 = (GladeCommandSetName *) this_cmd; + + return (g_strcmp0 (cmd1->old_name, cmd1->name) == 0); + } + return FALSE; + } + + if (GLADE_IS_COMMAND_SET_NAME (this_cmd) && + GLADE_IS_COMMAND_SET_NAME (other_cmd)) + { + cmd1 = GLADE_COMMAND_SET_NAME (this_cmd); + cmd2 = GLADE_COMMAND_SET_NAME (other_cmd); + + return (cmd1->widget == cmd2->widget); + } + + return FALSE; +} + +static void +glade_command_set_name_collapse (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd); + GladeCommandSetName *nthis = GLADE_COMMAND_SET_NAME (this_cmd); + GladeCommandSetName *nother = GLADE_COMMAND_SET_NAME (other_cmd); + + g_return_if_fail (GLADE_IS_COMMAND_SET_NAME (this_cmd) && + GLADE_IS_COMMAND_SET_NAME (other_cmd)); + + g_free (nthis->old_name); + nthis->old_name = nother->old_name; + nother->old_name = NULL; + + g_free (this_priv->description); + this_priv->description = + g_strdup_printf (_("Renaming %s to %s"), nthis->name, nthis->old_name); +} + +/* this function takes the ownership of name */ +void +glade_command_set_name (GladeWidget *widget, const gchar *name) +{ + GladeCommandSetName *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + + g_return_if_fail (GLADE_IS_WIDGET (widget)); + g_return_if_fail (name && name[0]); + + /* Dont spam the queue with false name changes. + */ + if (!strcmp (glade_widget_get_name (widget), name)) + return; + + me = g_object_new (GLADE_TYPE_COMMAND_SET_NAME, NULL); + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + priv->project = glade_widget_get_project (widget); + + me->widget = widget; + me->name = g_strdup (name); + me->old_name = g_strdup (glade_widget_get_name (widget)); + + priv->description = + g_strdup_printf (_("Renaming %s to %s"), me->old_name, me->name); + + glade_command_check_group (cmd); + + if (glade_command_set_name_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); +} + +/****************************************************************************** + * + * add/remove + * + * These canonical commands add/remove a widget list to/from the project. + * + *****************************************************************************/ + +struct _GladeCommandAddRemove +{ + GladeCommand parent; + + GList *widgets; + gboolean add; + gboolean from_clipboard; +}; + +#define GLADE_TYPE_COMMAND_ADD_REMOVE glade_command_add_remove_get_type () +GLADE_MAKE_COMMAND (GladeCommandAddRemove, glade_command_add_remove, COMMAND_ADD_REMOVE) + +static void +glade_command_placeholder_destroyed (GtkWidget *object, CommandData *cdata) +{ + if (GTK_WIDGET (cdata->placeholder) == object) + { + cdata->placeholder = NULL; + cdata->handler_id = 0; + } +} + +static void +glade_command_placeholder_connect (CommandData *cdata, + GladePlaceholder *placeholder) +{ + g_assert (cdata && cdata->placeholder == NULL); + + /* Something like a no-op with no placeholder */ + if ((cdata->placeholder = placeholder) == NULL) + return; + + cdata->handler_id = g_signal_connect + (placeholder, "destroy", + G_CALLBACK (glade_command_placeholder_destroyed), cdata); +} + +/** + * get_all_parentless_reffed_widgetst: + * + * @props (element-type GladeWidget) : List of #GladeWidget + * @return (element-type GladeWidget) : List of #GladeWidget + */ +static GList * +get_all_parentless_reffed_widgets (GList *reffed, GladeWidget *widget) +{ + GList *children, *l, *list; + GladeWidget *child; + + if ((list = glade_widget_get_parentless_reffed_widgets (widget)) != NULL) + reffed = g_list_concat (reffed, list); + + children = glade_widget_get_children (widget); + + for (l = children; l; l = l->next) + { + child = glade_widget_get_from_gobject (l->data); + reffed = get_all_parentless_reffed_widgets (reffed, child); + } + + g_list_free (children); + + return reffed; +} + +/** + * glade_command_add: + * @widgets: (element-type GladeWidget): a #GList + * @parent: a #GladeWidget + * @placeholder: a #GladePlaceholder + * @project: a #GladeProject + * @pasting: whether we are pasting an existing widget or creating a new one. + * + * Performs an add command on all widgets in @widgets to @parent, possibly + * replacing @placeholder (note toplevels don't need a parent; the active project + * will be used when pasting toplevel objects). + * Pasted widgets will persist packing properties from their cut/copy source + * while newly added widgets will prefer packing defaults. + * + */ +void +glade_command_add (GList *widgets, + GladeWidget *parent, + GladePlaceholder *placeholder, + GladeProject *project, + gboolean pasting) +{ + GladeCommandAddRemove *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + CommandData *cdata; + GladeWidget *widget = NULL; + GladeWidgetAdaptor *adaptor; + GList *l, *list, *children, *placeholders = NULL; + GtkWidget *child; + + g_return_if_fail (widgets && widgets->data); + g_return_if_fail (parent == NULL || GLADE_IS_WIDGET (parent)); + + me = g_object_new (GLADE_TYPE_COMMAND_ADD_REMOVE, NULL); + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + me->add = TRUE; + me->from_clipboard = pasting; + + /* Things can go wrong in this function if the dataset is inaccurate, + * we make no real attempt here to recover, just g_critical() and + * fix the bugs as they pop up. + */ + widget = GLADE_WIDGET (widgets->data); + adaptor = glade_widget_get_adaptor (widget); + + if (placeholder && GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor) == FALSE) + priv->project = glade_placeholder_get_project (placeholder); + else + priv->project = project; + + priv->description = + g_strdup_printf (_("Add %s"), g_list_length (widgets) == 1 ? + glade_widget_get_name (widget) : _("multiple")); + + for (list = widgets; list && list->data; list = list->next) + { + widget = list->data; + cdata = g_new0 (CommandData, 1); + if (glade_widget_get_internal (widget)) + g_critical ("Internal widget in Add"); + + adaptor = glade_widget_get_adaptor (widget); + + /* Widget */ + cdata->widget = g_object_ref (GLADE_WIDGET (widget)); + + /* Parentless ref */ + if ((cdata->reffed = + get_all_parentless_reffed_widgets (cdata->reffed, widget)) != NULL) + glade_util_list_objects_ref (cdata->reffed); + + /* Parent */ + if (parent == NULL) + cdata->parent = glade_widget_get_parent (widget); + else if (placeholder && GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor) == FALSE) + cdata->parent = glade_placeholder_get_parent (placeholder); + else + cdata->parent = parent; + + /* Placeholder */ + if (placeholder != NULL && g_list_length (widgets) == 1) + { + glade_command_placeholder_connect (cdata, placeholder); + } + else if (cdata->parent && + glade_widget_placeholder_relation (cdata->parent, widget)) + { + if ((children = + glade_widget_adaptor_get_children (glade_widget_get_adaptor (cdata->parent), + glade_widget_get_object (cdata->parent))) != NULL) + { + for (l = children; l && l->data; l = l->next) + { + child = l->data; + + /* Find a placeholder for this child, ignore special child types */ + if (GLADE_IS_PLACEHOLDER (child) && + g_object_get_data (G_OBJECT (child), "special-child-type") == NULL && + g_list_find (placeholders, child) == NULL) + { + placeholders = g_list_append (placeholders, child); + glade_command_placeholder_connect (cdata, GLADE_PLACEHOLDER (child)); + break; + } + } + g_list_free (children); + } + } + + me->widgets = g_list_prepend (me->widgets, cdata); + } + + glade_command_check_group (cmd); + + /* + * Push it onto the undo stack only on success + */ + if (glade_command_add_remove_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); + + if (placeholders) + g_list_free (placeholders); + +} + +static void +glade_command_delete_prop_refs (GladeWidget *widget) +{ + GladeProperty *property; + GList *refs, *l; + + refs = glade_widget_list_prop_refs (widget); + for (l = refs; l; l = l->next) + { + property = l->data; + glade_command_set_property (property, NULL); + } + g_list_free (refs); +} + +static void glade_command_remove (GList *widgets); + +static void +glade_command_remove_locked (GladeWidget *widget, GList *reffed) +{ + GList list = { 0, }, *widgets, *l; + GladeWidget *locked; + + widgets = glade_widget_list_locked_widgets (widget); + + for (l = widgets; l; l = l->next) + { + locked = l->data; + list.data = locked; + + if (g_list_find (reffed, locked)) + continue; + + glade_command_unlock_widget (locked); + glade_command_remove (&list); + } + + g_list_free (widgets); +} + +/** + * glade_command_remove: + * @widgets: (element-type GladeWidget): a #GList of #GladeWidgets + * @return_placeholders: whether or not to return a list of placehodlers + * + * Performs a remove command on all widgets in @widgets from @parent. + */ +static void +glade_command_remove (GList *widgets) +{ + GladeCommandAddRemove *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + GladeWidget *widget = NULL; + GladeWidget *lock; + CommandData *cdata; + GtkWidget *placeholder; + GList *list, *l; + + g_return_if_fail (widgets != NULL); + + /* internal children cannot be deleted. Notify the user. */ + for (list = widgets; list && list->data; list = list->next) + { + widget = list->data; + lock = glade_widget_get_locker (widget); + + if (glade_widget_get_internal (widget)) + { + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_WARN, NULL, + _ + ("You cannot remove a widget internal to a composite widget.")); + return; + } + else if (lock) + { + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_WARN, NULL, + _("%s is locked by %s, edit %s first."), + glade_widget_get_name (widget), + glade_widget_get_name (lock), + glade_widget_get_name (lock)); + return; + } + } + + me = g_object_new (GLADE_TYPE_COMMAND_ADD_REMOVE, NULL); + me->add = FALSE; + me->from_clipboard = FALSE; + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + + priv->project = glade_widget_get_project (widget); + priv->description = g_strdup ("dummy"); + + if (g_list_length (widgets) == 1) + glade_command_push_group (_("Remove %s"), + glade_widget_get_name (GLADE_WIDGET (widgets->data))); + else + glade_command_push_group (_("Remove multiple")); + + for (list = widgets; list && list->data; list = list->next) + { + widget = list->data; + + cdata = g_new0 (CommandData, 1); + cdata->widget = g_object_ref (GLADE_WIDGET (widget)); + cdata->parent = glade_widget_get_parent (widget); + + if ((cdata->reffed = + get_all_parentless_reffed_widgets (cdata->reffed, widget)) != NULL) + glade_util_list_objects_ref (cdata->reffed); + + /* If we're removing the template widget, then we need to unset it as template */ + if (glade_project_get_template (priv->project) == widget) + glade_command_set_project_template (priv->project, NULL); + + /* Undoably unset any object properties that may point to the removed object */ + glade_command_delete_prop_refs (widget); + + /* Undoably unlock and remove any widgets locked by this widget */ + glade_command_remove_locked (widget, cdata->reffed); + + if (cdata->parent != NULL && + glade_widget_placeholder_relation (cdata->parent, cdata->widget)) + { + placeholder = glade_placeholder_new (); + glade_command_placeholder_connect + (cdata, GLADE_PLACEHOLDER (placeholder)); + } + me->widgets = g_list_prepend (me->widgets, cdata); + + /* Record packing props if not deleted from the clipboard */ + if (me->from_clipboard == FALSE) + { + for (l = glade_widget_get_packing_properties (widget); l; l = l->next) + cdata->pack_props = + g_list_prepend (cdata->pack_props, + glade_property_dup (GLADE_PROPERTY (l->data), + cdata->widget)); + } + } + + g_assert (widget); + + glade_command_check_group (cmd); + + if (glade_command_add_remove_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); + + glade_command_pop_group (); +} /* end of glade_command_remove() */ + +static void +glade_command_transfer_props (GladeWidget *gnew, GList *saved_props) +{ + GList *l; + + for (l = saved_props; l; l = l->next) + { + GladeProperty *prop, *sprop = l->data; + GladePropertyDef *pdef = glade_property_get_def (sprop); + + prop = glade_widget_get_pack_property (gnew, glade_property_def_id (pdef)); + + if (prop && glade_property_def_transfer_on_paste (pdef) && + glade_property_def_match (glade_property_get_def (prop), pdef)) + glade_property_set_value (prop, glade_property_inline_value (sprop)); + } +} + +static gboolean +glade_command_add_execute (GladeCommandAddRemove *me) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private ((GladeCommand *) me); + CommandData *cdata; + GList *list, *l, *saved_props; + gchar *special_child_type; + + if (me->widgets) + { + glade_project_selection_clear (priv->project, FALSE); + + for (list = me->widgets; list && list->data; list = list->next) + { + cdata = list->data; + saved_props = NULL; + + GLADE_NOTE (COMMANDS, + g_print ("Adding widget '%s' to parent '%s' " + "(from clipboard: %s, props recorded: %s, have placeholder: %s, child_type: %s)\n", + glade_widget_get_name (cdata->widget), + cdata->parent ? glade_widget_get_name (cdata->parent) : "(none)", + me->from_clipboard ? "yes" : "no", + cdata->props_recorded ? "yes" : "no", + cdata->placeholder ? "yes" : "no", + cdata->special_type)); + + if (cdata->parent != NULL) + { + /* Prepare special-child-type for the paste. */ + if (me->from_clipboard) + { + /* Only transfer properties when they are from the clipboard, + * otherwise prioritize packing defaults. + */ + saved_props = + glade_widget_dup_properties (cdata->widget, + glade_widget_get_packing_properties (cdata->widget), + FALSE, FALSE, FALSE); + + glade_widget_set_packing_properties (cdata->widget, cdata->parent); + } + + /* Clear it the first time around, ensure we record it after adding */ + if (cdata->props_recorded == FALSE) + g_object_set_data (glade_widget_get_object (cdata->widget), + "special-child-type", NULL); + else + g_object_set_data_full (glade_widget_get_object (cdata->widget), + "special-child-type", + g_strdup (cdata->special_type), + g_free); + + /* glade_command_paste ganauntees that if + * there we are pasting to a placeholder, + * there is only one widget. + */ + if (cdata->placeholder) + glade_widget_replace (cdata->parent, + G_OBJECT (cdata->placeholder), + glade_widget_get_object (cdata->widget)); + else + glade_widget_add_child (cdata->parent, + cdata->widget, + cdata->props_recorded == FALSE); + + glade_command_transfer_props (cdata->widget, saved_props); + + if (saved_props) + g_list_free_full (saved_props, g_object_unref); + + /* Now that we've added, apply any packing props if nescisary. */ + for (l = cdata->pack_props; l; l = l->next) + { + GValue value = { 0, }; + GladeProperty *saved_prop = l->data; + GladePropertyDef *pdef = glade_property_get_def (saved_prop); + GladeProperty *widget_prop = + glade_widget_get_pack_property (cdata->widget, glade_property_def_id (pdef)); + + glade_property_get_value (saved_prop, &value); + glade_property_set_value (widget_prop, &value); + glade_property_sync (widget_prop); + g_value_unset (&value); + } + + if (cdata->props_recorded == FALSE) + { + + /* Save the packing properties after the initial paste. + * (this will be the defaults returned by the container + * implementation after initially adding them). + * + * Otherwise this recorded marker was set when cutting + */ + g_assert (cdata->pack_props == NULL); + for (l = glade_widget_get_packing_properties (cdata->widget); l; l = l->next) + cdata->pack_props = + g_list_prepend (cdata->pack_props, + glade_property_dup (GLADE_PROPERTY (l->data), + cdata->widget)); + + /* Record the special-type here after replacing */ + if ((special_child_type = + g_object_get_data (glade_widget_get_object (cdata->widget), + "special-child-type")) != NULL) + { + g_free (cdata->special_type); + cdata->special_type = g_strdup (special_child_type); + } + + GLADE_NOTE (COMMANDS, + g_print ("Recorded properties for adding widget '%s' to parent '%s' (special child: %s)\n", + glade_widget_get_name (cdata->widget), + cdata->parent ? glade_widget_get_name (cdata->parent) : "(none)", + cdata->special_type)); + + /* Mark the properties as recorded */ + cdata->props_recorded = TRUE; + } + } + + glade_project_add_object (priv->project, + glade_widget_get_object (cdata->widget)); + + for (l = cdata->reffed; l; l = l->next) + { + GladeWidget *reffed = l->data; + glade_project_add_object (priv->project, + glade_widget_get_object (reffed)); + } + + glade_project_selection_add (priv->project, + glade_widget_get_object (cdata->widget), FALSE); + + glade_widget_show (cdata->widget); + } + + glade_project_queue_selection_changed (priv->project); + } + return TRUE; +} /* end of glade_command_add_execute() */ + +static gboolean +glade_command_remove_execute (GladeCommandAddRemove *me) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private ((GladeCommand *) me); + CommandData *cdata; + GladeWidget *reffed; + GList *list, *l; + gchar *special_child_type; + + for (list = me->widgets; list && list->data; list = list->next) + { + cdata = list->data; + + GLADE_NOTE (COMMANDS, + g_print ("Removing widget '%s' from parent '%s' " + "(from clipboard: %s, props recorded: %s, have placeholder: %s, child_type: %s)\n", + glade_widget_get_name (cdata->widget), + cdata->parent ? glade_widget_get_name (cdata->parent) : "(none)", + me->from_clipboard ? "yes" : "no", + cdata->props_recorded ? "yes" : "no", + cdata->placeholder ? "yes" : "no", + cdata->special_type)); + + glade_widget_hide (cdata->widget); + + if (cdata->props_recorded == FALSE) + { + /* Record the special-type here after replacing */ + if ((special_child_type = + g_object_get_data (glade_widget_get_object (cdata->widget), + "special-child-type")) != NULL) + { + g_free (cdata->special_type); + cdata->special_type = g_strdup (special_child_type); + } + + GLADE_NOTE (COMMANDS, + g_print ("Recorded properties for removing widget '%s' from parent '%s' (special child: %s)\n", + glade_widget_get_name (cdata->widget), + cdata->parent ? glade_widget_get_name (cdata->parent) : "(none)", + cdata->special_type)); + + /* Mark the properties as recorded */ + cdata->props_recorded = TRUE; + } + + glade_project_remove_object (priv->project, + glade_widget_get_object (cdata->widget)); + + for (l = cdata->reffed; l; l = l->next) + { + reffed = l->data; + glade_project_remove_object (priv->project, + glade_widget_get_object (reffed)); + } + + if (cdata->parent) + { + if (cdata->placeholder) + glade_widget_replace (cdata->parent, glade_widget_get_object (cdata->widget), + G_OBJECT (cdata->placeholder)); + else + glade_widget_remove_child (cdata->parent, cdata->widget); + } + } + + return TRUE; +} + +/* + * Execute the cmd and revert it. Ie, after the execution of this + * function cmd will point to the undo action + */ +static gboolean +glade_command_add_remove_execute (GladeCommand *cmd) +{ + GladeCommandAddRemove *me = (GladeCommandAddRemove *) cmd; + gboolean retval; + + if (me->add) + retval = glade_command_add_execute (me); + else + retval = glade_command_remove_execute (me); + + me->add = !me->add; + + return retval; +} + +static gboolean +glade_command_add_remove_undo (GladeCommand *cmd) +{ + return glade_command_add_remove_execute (cmd); +} + +static void +glade_command_data_free (gpointer data) +{ + CommandData *cdata = data; + + if (cdata->placeholder) + { + g_clear_signal_handler (&cdata->handler_id, cdata->placeholder); + + if (g_object_is_floating (G_OBJECT (cdata->placeholder))) + gtk_widget_destroy (GTK_WIDGET (cdata->placeholder)); + } + + g_clear_object (&cdata->widget); + g_list_free_full (cdata->reffed, g_object_unref); + + g_free (cdata); +} + +static void +glade_command_add_remove_finalize (GObject *obj) +{ + GladeCommandAddRemove *cmd; + + g_return_if_fail (GLADE_IS_COMMAND_ADD_REMOVE (obj)); + + cmd = GLADE_COMMAND_ADD_REMOVE (obj); + + g_list_free_full (cmd->widgets, glade_command_data_free); + + glade_command_finalize (obj); +} + +static gboolean +glade_command_add_remove_unifies (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + return FALSE; +} + +static void +glade_command_add_remove_collapse (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + g_return_if_reached (); +} + +/****************************************************************************** + * + * The following are command aliases. Their implementations are the actual + * glade commands. + * + *****************************************************************************/ + +/** + * glade_command_create: + * @adaptor: A #GladeWidgetAdaptor + * @parent: (nullable): the parent #GladeWidget to add the new widget to. + * @placeholder: (nullable): the placeholder which will be substituted by the widget + * @project: the project his widget belongs to. + * + * Creates a new widget using @adaptor and put in place of the @placeholder + * in the @project + * + * Returns: (transfer full): the newly created widget. + */ +GladeWidget * +glade_command_create (GladeWidgetAdaptor *adaptor, + GladeWidget *parent, + GladePlaceholder *placeholder, + GladeProject *project) +{ + GladeWidget *widget; + GList *widgets = NULL; + + g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL); + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + /* attempt to create the widget -- widget may be null, e.g. the user clicked cancel on a query */ + widget = glade_widget_adaptor_create_widget (adaptor, TRUE, + "parent", parent, + "project", project, NULL); + if (widget == NULL) + { + return NULL; + } + + if (parent && !glade_widget_add_verify (parent, widget, TRUE)) + { + g_object_ref_sink (widget); + g_object_unref (widget); + return NULL; + } + + widgets = g_list_prepend (widgets, widget); + glade_command_push_group (_("Create %s"), glade_widget_get_name (widget)); + glade_command_add (widgets, parent, placeholder, project, FALSE); + glade_command_pop_group (); + + g_list_free (widgets); + + /* Make selection change immediately when a widget is created */ + glade_project_selection_changed (project); + + return widget; +} + +/** + * glade_command_delete: + * @widgets: (element-type GladeWidget): a #GList of #GladeWidgets + * + * Performs a delete command on the list of widgets. + */ +void +glade_command_delete (GList *widgets) +{ + GladeWidget *widget; + + g_return_if_fail (widgets != NULL); + + widget = widgets->data; + glade_command_push_group (_("Delete %s"), + g_list_length (widgets) == 1 ? + glade_widget_get_name (widget) : _("multiple")); + glade_command_remove (widgets); + glade_command_pop_group (); +} + +/** + * glade_command_cut: + * @widgets: (element-type GladeWidget): a #GList of #GladeWidgets + * + * Removes the list of widgets and adds them to the clipboard. + */ +void +glade_command_cut (GList *widgets) +{ + GladeWidget *widget; + GList *l; + + g_return_if_fail (widgets != NULL); + + for (l = widgets; l; l = l->next) + g_object_set_data (G_OBJECT (l->data), "glade-command-was-cut", + GINT_TO_POINTER (TRUE)); + + widget = widgets->data; + glade_command_push_group (_("Cut %s"), + g_list_length (widgets) == 1 ? + glade_widget_get_name (widget) : _("multiple")); + glade_command_remove (widgets); + glade_command_pop_group (); + + glade_clipboard_add (glade_app_get_clipboard (), widgets); +} + +#if 0 +static void +glade_command_break_references_for_widget (GladeWidget *widget, GList *widgets) +{ + GList *l, *children; + + for (l = widget->properties; l; l = l->next) + { + property = l->data; + + if (glade_property_def_is_object (property->klass) && + property->klass->parentless_widget == FALSE) + { + GList *obj_list; + GObject *reffed_obj = NULL; + GladeWidget *reffed_widget; + + if (GLADE_IS_PARAM_SPEC_OBJECTS (klass->pspec)) + { + glade_property_get (property, &obj_list); + + } + else + { + glade_property_get (property, &reffed_obj); + } + } + } + + children = glade_widget_adaptor_get_children (widget->adaptor, + widget->object); + + for (l = children; l; l = l->next) + { + if ((child = glade_widget_get_from_gobject (l->data)) != NULL) + glade_command_break_references_for_widget (child, widgets); + } + + g_list_free (children); +} + +static void +glade_command_break_references (GladeProject *project, GList *widgets) +{ + GList *list; + GladeWidget *widget; + + for (list = widgets; list && list->data; list = list->next) + { + widget = l->data; + + if (project == widget->project) + continue; + + glade_command_break_references_for_widget (widget, widgets); + } + + +} +#endif + +/** + * glade_command_paste: + * @widgets: (element-type GladeWidget): a #GList of #GladeWidget + * @parent: (allow-none): a #GladeWidget + * @placeholder: (allow-none): a #GladePlaceholder + * @project: a #GladeProject + * + * Performs a paste command on all widgets in @widgets to @parent, possibly + * replacing @placeholder (note toplevels dont need a parent; the active project + * will be used when pasting toplevel objects). + */ +void +glade_command_paste (GList *widgets, + GladeWidget *parent, + GladePlaceholder *placeholder, + GladeProject *project) +{ + GList *list, *copied_widgets = NULL; + GladeWidget *copied_widget = NULL; + gboolean exact; + + g_return_if_fail (widgets != NULL); + + for (list = widgets; list && list->data; list = list->next) + { + exact = + GPOINTER_TO_INT (g_object_get_data + (G_OBJECT (list->data), "glade-command-was-cut")); + + copied_widget = glade_widget_dup (list->data, exact); + copied_widgets = g_list_prepend (copied_widgets, copied_widget); + } + + glade_command_push_group (_("Paste %s"), + g_list_length (widgets) == 1 ? + glade_widget_get_name (copied_widget) : _("multiple")); + + glade_command_add (copied_widgets, parent, placeholder, project, TRUE); + glade_command_pop_group (); + + if (copied_widgets) + g_list_free (copied_widgets); +} + +/** + * glade_command_dnd: + * @widgets: (element-type GladeWidget): a #GList of #GladeWidget + * @parent: (allow-none): a #GladeWidget + * @placeholder: (allow-none): a #GladePlaceholder + * + * Performs a drag-n-drop command, i.e. removes the list of widgets and adds them + * to the new parent, possibly replacing @placeholder (note toplevels dont need a + * parent; the active project will be used when pasting toplevel objects). + */ +void +glade_command_dnd (GList *widgets, + GladeWidget *parent, + GladePlaceholder *placeholder) +{ + GladeWidget *widget; + GladeProject *project; + + g_return_if_fail (widgets != NULL); + + widget = widgets->data; + + if (parent) + project = glade_widget_get_project (parent); + else if (placeholder) + project = glade_placeholder_get_project (placeholder); + else + project = glade_widget_get_project (widget); + + g_return_if_fail (project); + + glade_command_push_group (_("Drag %s and Drop to %s"), + g_list_length (widgets) == 1 ? + glade_widget_get_name (widget) : _("multiple"), + parent ? glade_widget_get_name (parent) : _("root")); + glade_command_remove (widgets); + glade_command_add (widgets, parent, placeholder, project, TRUE); + glade_command_pop_group (); +} + +/*********************************************************/ +/******* GLADE_COMMAND_ADD_SIGNAL *******/ +/*********************************************************/ + +/* create a new GladeCommandAddRemoveChangeSignal class. Objects of this class will + * encapsulate an "add or remove signal handler" operation */ +typedef enum +{ + GLADE_ADD, + GLADE_REMOVE, + GLADE_CHANGE +} GladeAddType; + +struct _GladeCommandAddSignal +{ + GladeCommand parent; + + GladeWidget *widget; + + GladeSignal *signal; + GladeSignal *new_signal; + + GladeAddType type; +}; + +/* standard macros */ +#define GLADE_TYPE_COMMAND_ADD_SIGNAL glade_command_add_signal_get_type () +GLADE_MAKE_COMMAND (GladeCommandAddSignal, glade_command_add_signal, COMMAND_ADD_SIGNAL) + +static void +glade_command_add_signal_finalize (GObject *obj) +{ + GladeCommandAddSignal *cmd = GLADE_COMMAND_ADD_SIGNAL (obj); + + g_clear_object (&cmd->widget); + g_clear_object (&cmd->signal); + g_clear_object (&cmd->new_signal); + + glade_command_finalize (obj); +} + +static gboolean +glade_command_add_signal_undo (GladeCommand *this_cmd) +{ + return glade_command_add_signal_execute (this_cmd); +} + +static gboolean +glade_command_add_signal_execute (GladeCommand *this_cmd) +{ + GladeCommandAddSignal *cmd = GLADE_COMMAND_ADD_SIGNAL (this_cmd); + GladeSignal *temp; + + switch (cmd->type) + { + case GLADE_ADD: + glade_widget_add_signal_handler (cmd->widget, cmd->signal); + cmd->type = GLADE_REMOVE; + break; + case GLADE_REMOVE: + glade_widget_remove_signal_handler (cmd->widget, cmd->signal); + cmd->type = GLADE_ADD; + break; + case GLADE_CHANGE: + glade_widget_change_signal_handler (cmd->widget, + cmd->signal, cmd->new_signal); + temp = cmd->signal; + cmd->signal = cmd->new_signal; + cmd->new_signal = temp; + break; + default: + break; + } + return TRUE; +} + +static gboolean +glade_command_add_signal_unifies (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + return FALSE; +} + +static void +glade_command_add_signal_collapse (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + g_return_if_reached (); +} + +static void +glade_command_add_remove_change_signal (GladeWidget *glade_widget, + const GladeSignal *signal, + const GladeSignal *new_signal, + GladeAddType type) +{ + GladeCommandAddSignal *me = GLADE_COMMAND_ADD_SIGNAL + (g_object_new (GLADE_TYPE_COMMAND_ADD_SIGNAL, NULL)); + GladeCommand *cmd = GLADE_COMMAND (me); + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + + /* we can only add/remove a signal to a widget that has been wrapped by a GladeWidget */ + g_assert (glade_widget != NULL); + g_assert (glade_widget_get_project (glade_widget) != NULL); + + me->widget = g_object_ref (glade_widget); + me->type = type; + me->signal = glade_signal_clone (signal); + me->new_signal = new_signal ? glade_signal_clone (new_signal) : NULL; + + priv->project = glade_widget_get_project (glade_widget); + priv->description = + g_strdup_printf (type == GLADE_ADD ? _("Add signal handler %s") : + type == GLADE_REMOVE ? _("Remove signal handler %s") : + _("Change signal handler %s"), + glade_signal_get_handler ((GladeSignal *)signal)); + + glade_command_check_group (cmd); + + if (glade_command_add_signal_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); +} + +/** + * glade_command_add_signal: + * @glade_widget: a #GladeWidget + * @signal: a #GladeSignal + * + * TODO: write me + */ +void +glade_command_add_signal (GladeWidget *glade_widget, const GladeSignal *signal) +{ + glade_command_add_remove_change_signal + (glade_widget, signal, NULL, GLADE_ADD); +} + +/** + * glade_command_remove_signal: + * @glade_widget: a #GladeWidget + * @signal: a #GladeSignal + * + * TODO: write me + */ +void +glade_command_remove_signal (GladeWidget *glade_widget, + const GladeSignal *signal) +{ + glade_command_add_remove_change_signal + (glade_widget, signal, NULL, GLADE_REMOVE); +} + +/** + * glade_command_change_signal: + * @glade_widget: a #GladeWidget + * @old_signal: a #GladeSignal + * @new_signal: a #GladeSignal + * + * TODO: write me + */ +void +glade_command_change_signal (GladeWidget *glade_widget, + const GladeSignal *old_signal, + const GladeSignal *new_signal) +{ + glade_command_add_remove_change_signal + (glade_widget, old_signal, new_signal, GLADE_CHANGE); +} + +/****************************************************************************** + * + * set i18n metadata + * + * This command sets the i18n metadata on a label property. + * + *****************************************************************************/ + +struct _GladeCommandSetI18n +{ + GladeCommand parent; + + GladeProperty *property; + gboolean translatable; + gchar *context; + gchar *comment; + gboolean old_translatable; + gchar *old_context; + gchar *old_comment; +}; + +#define GLADE_TYPE_COMMAND_SET_I18N glade_command_set_i18n_get_type () +GLADE_MAKE_COMMAND (GladeCommandSetI18n, glade_command_set_i18n, COMMAND_SET_I18N) + +static gboolean +glade_command_set_i18n_execute (GladeCommand *cmd) +{ + GladeCommandSetI18n *me = (GladeCommandSetI18n *) cmd; + gboolean temp_translatable; + gchar *temp_context; + gchar *temp_comment; + + /* sanity check */ + g_return_val_if_fail (me != NULL, TRUE); + g_return_val_if_fail (me->property != NULL, TRUE); + + /* set the new values in the property */ + glade_property_i18n_set_translatable (me->property, me->translatable); + glade_property_i18n_set_context (me->property, me->context); + glade_property_i18n_set_comment (me->property, me->comment); + + /* swap the current values with the old values to prepare for undo */ + temp_translatable = me->translatable; + temp_context = me->context; + temp_comment = me->comment; + me->translatable = me->old_translatable; + me->context = me->old_context; + me->comment = me->old_comment; + me->old_translatable = temp_translatable; + me->old_context = temp_context; + me->old_comment = temp_comment; + + return TRUE; +} + +static gboolean +glade_command_set_i18n_undo (GladeCommand *cmd) +{ + return glade_command_set_i18n_execute (cmd); +} + +static void +glade_command_set_i18n_finalize (GObject *obj) +{ + GladeCommandSetI18n *me; + + g_return_if_fail (GLADE_IS_COMMAND_SET_I18N (obj)); + + me = GLADE_COMMAND_SET_I18N (obj); + g_free (me->context); + g_free (me->comment); + g_free (me->old_context); + g_free (me->old_comment); + + glade_command_finalize (obj); +} + +static gboolean +glade_command_set_i18n_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + GladeCommandSetI18n *cmd1; + GladeCommandSetI18n *cmd2; + + if (GLADE_IS_COMMAND_SET_I18N (this_cmd) && + GLADE_IS_COMMAND_SET_I18N (other_cmd)) + { + cmd1 = GLADE_COMMAND_SET_I18N (this_cmd); + cmd2 = GLADE_COMMAND_SET_I18N (other_cmd); + + return (cmd1->property == cmd2->property); + } + + return FALSE; +} + +static void +glade_command_set_i18n_collapse (GladeCommand *this_cmd, + GladeCommand *other_cmd) +{ + /* this command is the one that will be used for an undo of the sequence of like commands */ + GladeCommandSetI18n *this = GLADE_COMMAND_SET_I18N (this_cmd); + + /* the other command contains the values that will be used for a redo */ + GladeCommandSetI18n *other = GLADE_COMMAND_SET_I18N (other_cmd); + + g_return_if_fail (GLADE_IS_COMMAND_SET_I18N (this_cmd) && + GLADE_IS_COMMAND_SET_I18N (other_cmd)); + + /* adjust this command to contain, as its old values, the other command's current values */ + this->old_translatable = other->old_translatable; + g_free (this->old_context); + g_free (this->old_comment); + this->old_context = other->old_context; + this->old_comment = other->old_comment; + other->old_context = NULL; + other->old_comment = NULL; +} + +/** + * glade_command_set_i18n: + * @property: a #GladeProperty + * @translatable: a #gboolean + * @context: a #const gchar * + * @comment: a #const gchar * + * + * Sets the i18n data on the property. + */ +void +glade_command_set_i18n (GladeProperty *property, + gboolean translatable, + const gchar *context, + const gchar *comment) +{ + GladeCommandSetI18n *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + + g_return_if_fail (property); + + /* check that something changed before continuing with the command */ + if (translatable == glade_property_i18n_get_translatable (property) && + !g_strcmp0 (glade_property_i18n_get_context (property), context) && + !g_strcmp0 (glade_property_i18n_get_comment (property), comment)) + return; + + /* load up the command */ + me = g_object_new (GLADE_TYPE_COMMAND_SET_I18N, NULL); + me->property = property; + me->translatable = translatable; + me->context = g_strdup (context); + me->comment = g_strdup (comment); + me->old_translatable = glade_property_i18n_get_translatable (property); + me->old_context = g_strdup (glade_property_i18n_get_context (property)); + me->old_comment = g_strdup (glade_property_i18n_get_comment (property)); + + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + priv->project = glade_widget_get_project (glade_property_get_widget (property)); + priv->description = g_strdup_printf (_("Setting i18n metadata")); + + glade_command_check_group (cmd); + + /* execute the command and push it on the stack if successful */ + if (glade_command_set_i18n_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); +} + +/****************************************************************************** + * + * This command sets protection warnings on widgets + * + *****************************************************************************/ + +struct _GladeCommandLock +{ + GladeCommand parent; + + GladeWidget *widget; + GladeWidget *locked; + gboolean locking; +}; + +#define GLADE_TYPE_COMMAND_LOCK glade_command_lock_get_type () +GLADE_MAKE_COMMAND (GladeCommandLock, glade_command_lock, COMMAND_LOCK); + +static gboolean +glade_command_lock_execute (GladeCommand *cmd) +{ + GladeCommandLock *me = (GladeCommandLock *) cmd; + + /* set the new policy */ + if (me->locking) + glade_widget_lock (me->widget, me->locked); + else + glade_widget_unlock (me->locked); + + /* swap the current values with the old values to prepare for undo */ + me->locking = !me->locking; + + return TRUE; +} + +static gboolean +glade_command_lock_undo (GladeCommand *cmd) +{ + return glade_command_lock_execute (cmd); +} + +static void +glade_command_lock_finalize (GObject *obj) +{ + GladeCommandLock *me = (GladeCommandLock *) obj; + + g_object_unref (me->widget); + g_object_unref (me->locked); + + glade_command_finalize (obj); +} + +static gboolean +glade_command_lock_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ +/* GladeCommandLock *cmd1; */ +/* GladeCommandLock *cmd2; */ + /* No point here, this command undoubtedly always runs in groups */ + return FALSE; +} + +static void +glade_command_lock_collapse (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + /* this command is the one that will be used for an undo of the sequence of like commands */ + //GladeCommandLock *this = GLADE_COMMAND_LOCK (this_cmd); + + /* the other command contains the values that will be used for a redo */ + //GladeCommandLock *other = GLADE_COMMAND_LOCK (other_cmd); + + g_return_if_fail (GLADE_IS_COMMAND_LOCK (this_cmd) && + GLADE_IS_COMMAND_LOCK (other_cmd)); + + /* no unify/collapse */ +} + +/** + * glade_command_lock_widget: + * @widget: A #GladeWidget + * @locked: The #GladeWidget to lock + * + * Sets @locked to be in a locked up state + * spoken for by @widget, locked widgets cannot + * be removed from the project until unlocked. + */ +void +glade_command_lock_widget (GladeWidget *widget, GladeWidget *locked) +{ + GladeCommandLock *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + + g_return_if_fail (GLADE_IS_WIDGET (widget)); + g_return_if_fail (GLADE_IS_WIDGET (locked)); + g_return_if_fail (glade_widget_get_locker (locked) == NULL); + + /* load up the command */ + me = g_object_new (GLADE_TYPE_COMMAND_LOCK, NULL); + me->widget = g_object_ref (widget); + me->locked = g_object_ref (locked); + me->locking = TRUE; + + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + priv->project = glade_widget_get_project (widget); + priv->description = + g_strdup_printf (_("Locking %s by widget %s"), + glade_widget_get_name (locked), + glade_widget_get_name (widget)); + + glade_command_check_group (cmd); + + /* execute the command and push it on the stack if successful + * this sets the actual policy + */ + if (glade_command_lock_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); + +} + + +/** + * glade_command_unlock_widget: + * @widget: A #GladeWidget + * + * Unlocks @widget so that it can be removed + * from the project again + * + */ +void +glade_command_unlock_widget (GladeWidget *widget) +{ + GladeCommandLock *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + + g_return_if_fail (GLADE_IS_WIDGET (widget)); + g_return_if_fail (GLADE_IS_WIDGET (glade_widget_get_locker (widget))); + + /* load up the command */ + me = g_object_new (GLADE_TYPE_COMMAND_LOCK, NULL); + me->widget = g_object_ref (glade_widget_get_locker (widget)); + me->locked = g_object_ref (widget); + me->locking = FALSE; + + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + priv->project = glade_widget_get_project (widget); + priv->description = + g_strdup_printf (_("Unlocking %s"), glade_widget_get_name (widget)); + + glade_command_check_group (cmd); + + /* execute the command and push it on the stack if successful + * this sets the actual policy + */ + if (glade_command_lock_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); + +} + + +/****************************************************************************** + * + * This command sets the target version of a GladeProject + * + *****************************************************************************/ +struct _GladeCommandTarget +{ + GladeCommand parent; + + gchar *catalog; + gint old_major; + gint old_minor; + gint new_major; + gint new_minor; +}; + +#define GLADE_TYPE_COMMAND_TARGET glade_command_target_get_type () +GLADE_MAKE_COMMAND (GladeCommandTarget, glade_command_target, COMMAND_TARGET); + +static gboolean +glade_command_target_execute (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + GladeCommandTarget *me = (GladeCommandTarget *) cmd; + + glade_project_set_target_version (priv->project, + me->catalog, + me->new_major, + me->new_minor); + + return TRUE; +} + +static gboolean +glade_command_target_undo (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + GladeCommandTarget *me = (GladeCommandTarget *) cmd; + + glade_project_set_target_version (priv->project, + me->catalog, + me->old_major, + me->old_minor); + + return TRUE; +} + +static void +glade_command_target_finalize (GObject *obj) +{ + GladeCommandTarget *me = (GladeCommandTarget *) obj; + + g_free (me->catalog); + + glade_command_finalize (obj); +} + +static gboolean +glade_command_target_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + GladeCommandTarget *me; + + /* Do we unify with self ? */ + if (!other_cmd) + { + if (GLADE_IS_COMMAND_TARGET (this_cmd)) + { + me = (GladeCommandTarget *) this_cmd; + + return (me->old_major == me->new_major && + me->old_minor == me->new_minor); + } + return FALSE; + } + + if (GLADE_IS_COMMAND_TARGET (this_cmd) && + GLADE_IS_COMMAND_TARGET (other_cmd)) + { + GladeCommandTarget *other; + + me = (GladeCommandTarget *) this_cmd; + other = (GladeCommandTarget *) other_cmd; + + return g_strcmp0 (me->catalog, other->catalog) == 0; + } + + return FALSE; +} + +static void +glade_command_target_collapse (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + GladeCommandPrivate *this_priv = glade_command_get_instance_private (this_cmd); + GladeCommandTarget *this; + GladeCommandTarget *other; + + g_return_if_fail (GLADE_IS_COMMAND_TARGET (this_cmd) && + GLADE_IS_COMMAND_TARGET (other_cmd)); + + this = GLADE_COMMAND_TARGET (this_cmd); + other = GLADE_COMMAND_TARGET (other_cmd); + + this->new_major = other->new_major; + this->new_minor = other->new_minor; + + g_free (this_priv->description); + this_priv->description = + g_strdup_printf (_("Setting target version of '%s' to %d.%d"), + this->catalog, this->new_major, this->new_minor); + +} + +/** + * glade_command_set_project_target: + * @project: A #GladeProject + * @catalog: The name of the catalog to set the project's target for + * @major: The new major version of @catalog to target + * @minor: The new minor version of @catalog to target + * + * Sets the target of @catalog to @major.@minor in @project. + */ +void +glade_command_set_project_target (GladeProject *project, + const gchar *catalog, + gint major, + gint minor) +{ + GladeCommandTarget *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + gint old_major = 0; + gint old_minor = 0; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (catalog && catalog[0]); + g_return_if_fail (major >= 0); + g_return_if_fail (minor >= 0); + + /* load up the command */ + me = g_object_new (GLADE_TYPE_COMMAND_TARGET, NULL); + me->catalog = g_strdup (catalog); + + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + priv->project = project; + + glade_project_get_target_version (project, me->catalog, &old_major, &old_minor); + + me->new_major = major; + me->new_minor = minor; + me->old_major = old_major; + me->old_minor = old_minor; + + priv->description = + g_strdup_printf (_("Setting target version of '%s' to %d.%d"), + me->catalog, me->new_major, me->new_minor); + + glade_command_check_group (cmd); + + /* execute the command and push it on the stack if successful + * this sets the actual policy + */ + if (glade_command_target_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); +} + +/****************************************************************************** + * + * This command sets can set different properties of a GladeProject + * + *****************************************************************************/ +typedef gchar *(*DescriptionNewFunc) (GladeCommand *); + +struct _GladeCommandProperty +{ + GladeCommand parent; + + const gchar *property_id; /* Intern string */ + DescriptionNewFunc description_new; /* Used to update command description */ + GValue old_value; + GValue new_value; +}; + +#define GLADE_TYPE_COMMAND_PROPERTY glade_command_property_get_type () +GLADE_MAKE_COMMAND (GladeCommandProperty, glade_command_property, COMMAND_PROPERTY) + +/* Return true if a == b, this could be exported in glade_utils */ +static gboolean +glade_command_property_compare (GValue *a, GValue *b) +{ + if (G_VALUE_TYPE (a) != G_VALUE_TYPE (b)) + { + g_warning ("Comparing a %s with a %s type is not supported", + G_VALUE_TYPE_NAME (a), G_VALUE_TYPE_NAME (b)); + return FALSE; + } + + if (G_VALUE_HOLDS_STRING (a)) + return g_strcmp0 (g_value_get_string (a), g_value_get_string (b)) == 0; + else if (G_VALUE_HOLDS_OBJECT (a)) + return g_value_get_object (a) == g_value_get_object (b); + else if (G_VALUE_HOLDS_BOOLEAN (a)) + return g_value_get_boolean (a) == g_value_get_boolean (b); + else if (G_VALUE_HOLDS_CHAR (a)) + return g_value_get_schar (a) == g_value_get_schar (b); + else if (G_VALUE_HOLDS_DOUBLE (a)) + return g_value_get_double (a) == g_value_get_double (b); + else if (G_VALUE_HOLDS_ENUM (a)) + return g_value_get_enum (a) == g_value_get_enum (b); + else if (G_VALUE_HOLDS_FLAGS (a)) + return g_value_get_flags (a) == g_value_get_flags (b); + else if (G_VALUE_HOLDS_FLOAT (a)) + return g_value_get_float (a) == g_value_get_float (b); + else if (G_VALUE_HOLDS_GTYPE (a)) + return g_value_get_gtype (a) == g_value_get_gtype (b); + else if (G_VALUE_HOLDS_INT (a)) + return g_value_get_int (a) == g_value_get_int (b); + else if (G_VALUE_HOLDS_INT64 (a)) + return g_value_get_int64 (a) == g_value_get_int64 (b); + else if (G_VALUE_HOLDS_LONG (a)) + return g_value_get_long (a) == g_value_get_long (b); + else if (G_VALUE_HOLDS_POINTER (a)) + return g_value_get_pointer (a) == g_value_get_pointer (b); + else if (G_VALUE_HOLDS_UCHAR (a)) + return g_value_get_uchar (a) == g_value_get_uchar (b); + else if (G_VALUE_HOLDS_UINT (a)) + return g_value_get_uint (a) == g_value_get_uint (b); + else if (G_VALUE_HOLDS_UINT64 (a)) + return g_value_get_uint64 (a) == g_value_get_uint64 (b); + else if (G_VALUE_HOLDS_ULONG (a)) + return g_value_get_ulong (a) == g_value_get_ulong (b); + + g_warning ("%s type not supported", G_VALUE_TYPE_NAME (a)); + return FALSE; +} + +static gboolean +glade_command_property_execute (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + GladeCommandProperty *me = (GladeCommandProperty *) cmd; + + g_object_set_property (G_OBJECT (priv->project), me->property_id, &me->new_value); + + return TRUE; +} + +static gboolean +glade_command_property_undo (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + GladeCommandProperty *me = (GladeCommandProperty *) cmd; + + g_object_set_property (G_OBJECT (priv->project), me->property_id, &me->old_value); + + return TRUE; +} + +static void +glade_command_property_finalize (GObject *obj) +{ + GladeCommandProperty *me = (GladeCommandProperty *) obj; + + /* NOTE: we do not free me->property_id because it is an intern string */ + g_value_unset (&me->new_value); + g_value_unset (&me->old_value); + + glade_command_finalize (obj); +} + +static gboolean +glade_command_property_unifies (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + /* Do we unify with self ? */ + if (!other_cmd) + { + if (GLADE_IS_COMMAND_PROPERTY (this_cmd)) + { + GladeCommandProperty *me = (GladeCommandProperty *) this_cmd; + return glade_command_property_compare (&me->new_value, &me->old_value); + } + else + return FALSE; + } + + if (GLADE_IS_COMMAND_PROPERTY (this_cmd) && GLADE_IS_COMMAND_PROPERTY (other_cmd)) + { + GladeCommandProperty *this = (GladeCommandProperty *) this_cmd; + GladeCommandProperty *other = (GladeCommandProperty *) other_cmd; + + /* Intern strings can be compared by comparing the pointers */ + return this->property_id == other->property_id; + } + + return FALSE; +} + +static void +glade_command_property_update_description (GladeCommand *cmd) +{ + GladeCommandPrivate *priv = glade_command_get_instance_private (cmd); + GladeCommandProperty *me = (GladeCommandProperty *) cmd; + + g_free (priv->description); + + if (me->description_new) + priv->description = me->description_new (cmd); + else + priv->description = g_strdup_printf (_("Setting project's %s property"), + me->property_id); +} + +static void +glade_command_property_collapse (GladeCommand *this_cmd, GladeCommand *other_cmd) +{ + GladeCommandProperty *this; + GladeCommandProperty *other; + + g_return_if_fail (GLADE_IS_COMMAND_PROPERTY (this_cmd) && + GLADE_IS_COMMAND_PROPERTY (other_cmd)); + + this = GLADE_COMMAND_PROPERTY (this_cmd); + other = GLADE_COMMAND_PROPERTY (other_cmd); + + g_return_if_fail (this->property_id == other->property_id); + + g_value_copy (&other->new_value, &this->new_value); + + glade_command_property_update_description (this_cmd); +} + +/** + * glade_command_set_project_property: + * @project: A #GladeProject + * @description_new: function to create the command description. + * @property_id: property this command should use + * @new_value: the value to set @property_id + * + * Sets @new_value as the @property_id property for @project. + */ +static void +glade_command_set_project_property (GladeProject *project, + DescriptionNewFunc description_new, + const gchar *property_id, + GValue *new_value) +{ + GladeCommandProperty *me; + GladeCommand *cmd; + GladeCommandPrivate *priv; + GValue old_value = G_VALUE_INIT; + + g_value_init (&old_value, G_VALUE_TYPE (new_value)); + g_object_get_property (G_OBJECT (project), property_id, &old_value); + + if (glade_command_property_compare (&old_value, new_value)) + { + g_value_unset (&old_value); + return; + } + + me = g_object_new (GLADE_TYPE_COMMAND_PROPERTY, NULL); + cmd = GLADE_COMMAND (me); + priv = glade_command_get_instance_private (cmd); + priv->project = project; + + me->description_new = description_new; + me->property_id = g_intern_static_string (property_id); + + /* move the old value to the command struct */ + me->old_value = old_value; + + /* set new value */ + g_value_init (&me->new_value, G_VALUE_TYPE (new_value)); + g_value_copy (new_value, &me->new_value); + + glade_command_property_update_description (cmd); + + glade_command_check_group (cmd); + + /* execute the command and push it on the stack if successful + * this sets the actual policy + */ + if (glade_command_property_execute (cmd)) + glade_project_push_undo (priv->project, cmd); + else + g_object_unref (G_OBJECT (me)); +} + +/** + * glade_command_set_project_license: + * @project: A #GladeProject + * @license: License of @project + * + * Sets the license agreement for @project. It will be saved in the xml as comment. + */ +void +glade_command_set_project_license (GladeProject *project, const gchar *license) +{ + GValue new_value = G_VALUE_INIT; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + g_value_init (&new_value, G_TYPE_STRING); + g_value_set_string (&new_value, license); + + glade_command_set_project_property (project, NULL, "license", &new_value); + + g_value_unset (&new_value); +} + +static gchar * +gcp_resource_path_description_new (GladeCommand *cmd) +{ + GladeCommandProperty *me = (GladeCommandProperty *) cmd; + + return g_strdup_printf (_("Setting resource path to '%s'"), + g_value_get_string (&me->new_value)); +} + +/** + * glade_command_set_project_resource_path: + * @project: A #GladeProject + * @path: path to load resources from. + * + * Sets a resource path @project. + */ +void +glade_command_set_project_resource_path (GladeProject *project, const gchar *path) +{ + GValue new_value = G_VALUE_INIT; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + g_value_init (&new_value, G_TYPE_STRING); + g_value_set_string (&new_value, path); + + glade_command_set_project_property (project, gcp_resource_path_description_new, + "resource-path", &new_value); + g_value_unset (&new_value); +} + +static gchar * +gcp_domain_description_new (GladeCommand *cmd) +{ + GladeCommandProperty *me = (GladeCommandProperty *) cmd; + + return g_strdup_printf (_("Setting translation domain to '%s'"), + g_value_get_string (&me->new_value)); +} + +/** + * glade_command_set_project_domain: + * @project: A #GladeProject + * @domain: The translation domain for @project + * + * Sets @domain as the translation domain for @project. + */ +void +glade_command_set_project_domain (GladeProject *project, + const gchar *domain) +{ + GValue new_value = G_VALUE_INIT; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + g_value_init (&new_value, G_TYPE_STRING); + g_value_set_string (&new_value, domain); + + glade_command_set_project_property (project, gcp_domain_description_new, + "translation-domain", &new_value); + g_value_unset (&new_value); +} + +static gchar * +gcp_template_description_new (GladeCommand *cmd) +{ + GladeCommandProperty *me = (GladeCommandProperty *) cmd; + GObject *new_template = g_value_get_object (&me->new_value); + GObject *old_template = g_value_get_object (&me->old_value); + + if (new_template == NULL && old_template != NULL) + return g_strdup_printf (_("Unsetting widget '%s' as template"), + glade_widget_get_name (GLADE_WIDGET (old_template))); + else if (new_template != NULL) + return g_strdup_printf (_("Setting widget '%s' as template"), + glade_widget_get_name (GLADE_WIDGET (new_template))); + else + return g_strdup (_("Unsetting template")); +} + +/** + * glade_command_set_project_template: + * @project: A #GladeProject + * @widget: The #GladeWidget to make template + * + * Sets @widget to be the template widget in @project. + */ +void +glade_command_set_project_template (GladeProject *project, + GladeWidget *widget) +{ + GValue new_value = G_VALUE_INIT; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + g_value_init (&new_value, G_TYPE_OBJECT); + g_value_set_object (&new_value, widget); + + glade_command_set_project_property (project, gcp_template_description_new, + "template", &new_value); + g_value_unset (&new_value); +} diff --git a/gladeui/glade-command.h b/gladeui/glade-command.h new file mode 100644 index 0000000..67e2deb --- /dev/null +++ b/gladeui/glade-command.h @@ -0,0 +1,160 @@ +#ifndef __GLADE_COMMAND_H__ +#define __GLADE_COMMAND_H__ + +#include +#include +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_COMMAND glade_command_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeCommand, glade_command, GLADE, COMMAND, GObject) + +typedef struct _GladeCommandSetPropData GladeCommandSetPropData; + +/** + * GladeCommandSetPropData + * @property: A #GladeProperty to set + * @new_value: The new #GValue to assign to @property + * @old_value: The old #GValue of @property + * + * #GladeProperty can be set in a list as one command, + * for Undo purposes; we store the list of #GladeCommandSetPropData with + * their old and new #GValue. + */ +struct _GladeCommandSetPropData { + GladeProperty *property; + GValue *new_value; + GValue *old_value; +}; + +struct _GladeCommandClass +{ + GObjectClass parent_class; + + gboolean (* execute) (GladeCommand *command); + gboolean (* undo) (GladeCommand *command); + gboolean (* unifies) (GladeCommand *command, GladeCommand *other); + void (* collapse) (GladeCommand *command, GladeCommand *other); + + gpointer padding[4]; +}; + +void glade_command_push_group (const gchar *fmt, + ...) G_GNUC_PRINTF (1, 2); +void glade_command_pop_group (void); +gint glade_command_get_group_depth (void); + +const gchar *glade_command_description (GladeCommand *command); +gint glade_command_group_id (GladeCommand *command); +gboolean glade_command_execute (GladeCommand *command); +gboolean glade_command_undo (GladeCommand *command); +gboolean glade_command_unifies (GladeCommand *command, + GladeCommand *other); +void glade_command_collapse (GladeCommand *command, + GladeCommand *other); + +/************************ project ******************************/ +void glade_command_set_project_target (GladeProject *project, + const gchar *catalog, + gint major, + gint minor); + +void glade_command_set_project_domain (GladeProject *project, + const gchar *domain); + +void glade_command_set_project_template(GladeProject *project, + GladeWidget *widget); + +void glade_command_set_project_license (GladeProject *project, + const gchar *license); + +void glade_command_set_project_resource_path (GladeProject *project, + const gchar *path); + +/************************** properties *********************************/ + +void glade_command_set_property_enabled(GladeProperty *property, + gboolean enabled); + +void glade_command_set_property (GladeProperty *property, + ...); + +void glade_command_set_property_value (GladeProperty *property, + const GValue *value); + +void glade_command_set_properties (GladeProperty *property, + const GValue *old_value, + const GValue *new_value, + ...); + +void glade_command_set_properties_list (GladeProject *project, + GList *props); /* list of GladeCommandSetPropData */ + +/************************** name ******************************/ + +void glade_command_set_name (GladeWidget *glade_widget, const gchar *name); + + +/************************ protection ******************************/ + +void glade_command_lock_widget (GladeWidget *widget, + GladeWidget *locked); + +void glade_command_unlock_widget (GladeWidget *widget); + + +/************************ create/add/delete ******************************/ + +void glade_command_add (GList *widgets, + GladeWidget *parent, + GladePlaceholder *placeholder, + GladeProject *project, + gboolean pasting); + +void glade_command_delete (GList *widgets); + +GladeWidget *glade_command_create (GladeWidgetAdaptor *adaptor, + GladeWidget *parent, + GladePlaceholder *placeholder, + GladeProject *project); + +/************************ cut/paste/dnd ******************************/ + +void glade_command_cut (GList *widgets); + +void glade_command_paste (GList *widgets, + GladeWidget *parent, + GladePlaceholder *placeholder, + GladeProject *project); + +void glade_command_dnd (GList *widgets, + GladeWidget *parent, + GladePlaceholder *placeholder); + +/************************ signals ******************************/ + +void glade_command_add_signal (GladeWidget *glade_widget, + const GladeSignal *signal); + +void glade_command_remove_signal (GladeWidget *glade_widget, + const GladeSignal *signal); + +void glade_command_change_signal (GladeWidget *glade_widget, + const GladeSignal *old_signal, + const GladeSignal *new_signal); + +/************************ set i18n ******************************/ + +void glade_command_set_i18n (GladeProperty *property, + gboolean translatable, + const gchar *context, + const gchar *comment); + + +G_END_DECLS + +#endif /* __GLADE_COMMAND_H__ */ diff --git a/gladeui/glade-cursor.c b/gladeui/glade-cursor.c new file mode 100644 index 0000000..8b73d0d --- /dev/null +++ b/gladeui/glade-cursor.c @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Chema Celorio + */ + +#include "config.h" + +#include "glade-app.h" +#include "glade-palette.h" +#include "glade-cursor.h" +#include "glade-widget-adaptor.h" + +#include +#include + +#define ADD_PIXBUF_FILENAME "plus.png" + +static GladeCursor *cursor = NULL; + +static void +set_cursor_recurse (GtkWidget * widget, GdkCursor * gdk_cursor) +{ + GList *children, *list; + + if (!gtk_widget_get_visible (widget) || !gtk_widget_get_realized (widget)) + return; + + gdk_window_set_cursor (gtk_widget_get_window (widget), gdk_cursor); + + if (GTK_IS_CONTAINER (widget) && + (children = + glade_util_container_get_all_children (GTK_CONTAINER (widget))) != NULL) + { + for (list = children; list; list = list->next) + { + set_cursor_recurse (GTK_WIDGET (list->data), gdk_cursor); + } + g_list_free (children); + } +} + +static void +set_cursor (GladeProject *project, + GdkCursor *gdk_cursor) +{ + GList *list; + + for (list = (GList *) glade_project_get_objects (project); + list; list = list->next) + { + GObject *object = list->data; + + if (GTK_IS_WIDGET (object) && + gtk_widget_get_has_window (GTK_WIDGET (object))) + { + set_cursor_recurse (GTK_WIDGET (object), gdk_cursor); + } + } +} + +/** + * glade_cursor_set: + * @window: a #GdkWindow + * @type: a #GladeCursorType + * + * Sets the cursor for @window to something appropriate based on @type. + * (also sets the cursor on all visible project widgets) + */ +void +glade_cursor_set (GladeProject *project, + GdkWindow *window, + GladeCursorType type) +{ + GladeWidgetAdaptor *adaptor; + GdkCursor *the_cursor = NULL; + g_return_if_fail (cursor != NULL); + + switch (type) + { + case GLADE_CURSOR_SELECTOR: + the_cursor = cursor->selector; + break; + case GLADE_CURSOR_ADD_WIDGET: + if ((adaptor = + glade_project_get_add_item (project)) != NULL) + { + g_object_get (adaptor, "cursor", &the_cursor, NULL); + + if (the_cursor == NULL) + the_cursor = cursor->add_widget; + } + else + the_cursor = cursor->add_widget; + break; + case GLADE_CURSOR_RESIZE_TOP_LEFT: + the_cursor = cursor->resize_top_left; + break; + case GLADE_CURSOR_RESIZE_TOP_RIGHT: + the_cursor = cursor->resize_top_right; + break; + case GLADE_CURSOR_RESIZE_BOTTOM_LEFT: + the_cursor = cursor->resize_bottom_left; + break; + case GLADE_CURSOR_RESIZE_BOTTOM_RIGHT: + the_cursor = cursor->resize_bottom_right; + break; + case GLADE_CURSOR_RESIZE_LEFT: + the_cursor = cursor->resize_left; + break; + case GLADE_CURSOR_RESIZE_RIGHT: + the_cursor = cursor->resize_right; + break; + case GLADE_CURSOR_RESIZE_TOP: + the_cursor = cursor->resize_top; + break; + case GLADE_CURSOR_RESIZE_BOTTOM: + the_cursor = cursor->resize_bottom; + break; + case GLADE_CURSOR_DRAG: + the_cursor = cursor->drag; + break; + default: + break; + } + + if (the_cursor != gdk_window_get_cursor (window)) + { + set_cursor (project, cursor->selector); + gdk_window_set_cursor (window, the_cursor); + } +} + +/** + * glade_cursor_init: + * + * Initializes cursors for use with glade_cursor_set(). + */ +void +glade_cursor_init (void) +{ + gchar *path; + GError *error = NULL; + GdkDisplay *display; + + cursor = g_new0 (GladeCursor, 1); + display = gdk_display_get_default (); + + cursor->selector = NULL; + cursor->add_widget = gdk_cursor_new_from_name (display, "crosshair"); + cursor->resize_top_left = gdk_cursor_new_from_name (display, "nw-resize"); + cursor->resize_top_right = gdk_cursor_new_from_name (display, "ne-resize"); + cursor->resize_bottom_left = gdk_cursor_new_from_name (display, "sw-resize"); + cursor->resize_bottom_right = gdk_cursor_new_from_name (display, "se-resize"); + cursor->resize_left = gdk_cursor_new_from_name (display, "w-resize"); + cursor->resize_right = gdk_cursor_new_from_name (display, "e-resize"); + cursor->resize_top = gdk_cursor_new_from_name (display, "n-resize"); + cursor->resize_bottom = gdk_cursor_new_from_name (display, "s-resize"); + cursor->drag = gdk_cursor_new_from_name (display, "move"); + cursor->add_widget_pixbuf = NULL; + + /* load "add" cursor pixbuf */ + path = + g_build_filename (glade_app_get_pixmaps_dir (), ADD_PIXBUF_FILENAME, + NULL); + + cursor->add_widget_pixbuf = gdk_pixbuf_new_from_file (path, &error); + + if (cursor->add_widget_pixbuf == NULL) + { + g_critical (_("Unable to load image (%s)"), error->message); + + g_error_free (error); + error = NULL; + } + g_free (path); +} + +const GdkPixbuf * +glade_cursor_get_add_widget_pixbuf (void) +{ + g_return_val_if_fail (cursor != NULL, NULL); + + return cursor->add_widget_pixbuf; +} diff --git a/gladeui/glade-cursor.h b/gladeui/glade-cursor.h new file mode 100644 index 0000000..e38b5e0 --- /dev/null +++ b/gladeui/glade-cursor.h @@ -0,0 +1,56 @@ +#ifndef __GLADE_CURSOR_H__ +#define __GLADE_CURSOR_H__ + +G_BEGIN_DECLS + +/* GladeCursor is just a structures that has a pointer to all the cursors + * that we are going to use. The benefit of this struct is that once + * glade_cursor_init is called you just need to call glade_cursor_set + * with it's enumed value to set the window cursor. + */ + +/* Has a pointer to the loaded GdkCursors. It is loaded when _init + * is called + */ +typedef struct _GladeCursor { + GdkCursor *selector; + GdkCursor *add_widget; /* fallback cursor if we cannot use widget_class->cursor */ + GdkCursor *resize_top_left; + GdkCursor *resize_top_right; + GdkCursor *resize_bottom_left; + GdkCursor *resize_bottom_right; + GdkCursor *resize_left; + GdkCursor *resize_right; + GdkCursor *resize_top; + GdkCursor *resize_bottom; + GdkCursor *drag; + + GdkPixbuf *add_widget_pixbuf; /* a pixbuf of the generic 'add' cursor */ +} GladeCursor; + +/* Enumed values for each of the cursors for GladeCursor. For every + * GdkCursor above there should be a enum here + */ +typedef enum { + GLADE_CURSOR_SELECTOR, + GLADE_CURSOR_ADD_WIDGET, + GLADE_CURSOR_RESIZE_TOP_LEFT, + GLADE_CURSOR_RESIZE_TOP_RIGHT, + GLADE_CURSOR_RESIZE_BOTTOM_LEFT, + GLADE_CURSOR_RESIZE_BOTTOM_RIGHT, + GLADE_CURSOR_RESIZE_LEFT, + GLADE_CURSOR_RESIZE_RIGHT, + GLADE_CURSOR_RESIZE_TOP, + GLADE_CURSOR_RESIZE_BOTTOM, + GLADE_CURSOR_DRAG +} GladeCursorType; + +void glade_cursor_init (void); +void glade_cursor_set (GladeProject *project, + GdkWindow *window, + GladeCursorType type); +const GdkPixbuf* glade_cursor_get_add_widget_pixbuf (void); + +G_END_DECLS + +#endif /* __GLADE_CURSOR_H__ */ diff --git a/gladeui/glade-debug.c b/gladeui/glade-debug.c new file mode 100644 index 0000000..aae2f41 --- /dev/null +++ b/gladeui/glade-debug.c @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2003 Joaquin Cuenca Abela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Joaquin Cuenca Abela + */ + +#include "config.h" + +#include "glade.h" +#include "glade-debug.h" + +#ifdef G_OS_UNIX +#include +#endif + +#ifndef RETSIGTYPE +#define RETSIGTYPE void +#endif + + +static void +glade_log_handler (const char *domain, + GLogLevelFlags level, const char *message, gpointer data) +{ + static volatile int want_breakpoint = 0; + + /* Ignore this message */ + if (g_strcmp0 ("gdk_window_set_composited called but compositing is not supported", message) != 0) + g_log_default_handler (domain, level, message, data); + + if (want_breakpoint && + ((level & (G_LOG_LEVEL_CRITICAL /* | G_LOG_LEVEL_WARNING */ )) != 0)) + G_BREAKPOINT (); +} + +static void +glade_set_log_handler (const char *domain) +{ + g_log_set_handler (domain, G_LOG_LEVEL_MASK, glade_log_handler, NULL); +} + +/** + * glade_setup_log_handlers: + * + * Sets up a log handler to manage all %G_LOG_LEVEL_MASK errors of domain: + * GLib, GLib-GObject, Gtk, Gdk, and domainless. + */ +void +glade_setup_log_handlers () +{ + glade_set_log_handler (""); + glade_set_log_handler ("GLib"); + glade_set_log_handler ("GLib-GObject"); + glade_set_log_handler ("Gtk"); + glade_set_log_handler ("Gdk"); +} + +static GladeDebugFlag glade_debug_flags = 0; + +static const GDebugKey glade_debug_keys[] = { + { "ref-counts", GLADE_DEBUG_REF_COUNTS }, + { "widget-events", GLADE_DEBUG_WIDGET_EVENTS }, + { "commands", GLADE_DEBUG_COMMANDS }, + { "properties", GLADE_DEBUG_PROPERTIES }, + { "verify", GLADE_DEBUG_VERIFY } +}; + +guint +glade_get_debug_flags (void) +{ + return glade_debug_flags; +} + +void +glade_init_debug_flags (void) +{ + static gboolean initialized = FALSE; + + if (G_UNLIKELY (!initialized)) + { + const gchar *env_string; + + initialized = TRUE; + + env_string = g_getenv ("GLADE_DEBUG"); + if (env_string != NULL) + glade_debug_flags = + g_parse_debug_string (env_string, + glade_debug_keys, + G_N_ELEMENTS (glade_debug_keys)); + } +} diff --git a/gladeui/glade-debug.h b/gladeui/glade-debug.h new file mode 100644 index 0000000..c591e2a --- /dev/null +++ b/gladeui/glade-debug.h @@ -0,0 +1,35 @@ +#ifndef __GLADE_DEBUG_H__ +#define __GLADE_DEBUG_H__ + +G_BEGIN_DECLS + +typedef enum { + GLADE_DEBUG_REF_COUNTS = (1 << 0), + GLADE_DEBUG_WIDGET_EVENTS = (1 << 1), + GLADE_DEBUG_COMMANDS = (1 << 2), + GLADE_DEBUG_PROPERTIES = (1 << 3), + GLADE_DEBUG_VERIFY = (1 << 4) +} GladeDebugFlag; + +#ifdef GLADE_ENABLE_DEBUG + +#define GLADE_NOTE(type,action) \ + G_STMT_START { \ + if (glade_get_debug_flags () & GLADE_DEBUG_##type) \ + { action; }; \ + } G_STMT_END + +#else /* !GLADE_ENABLE_DEBUG */ + +#define GLADE_NOTE(type, action) + +#endif /* GLADE_ENABLE_DEBUG */ + +void glade_init_debug_flags (void); +guint glade_get_debug_flags (void); + +void glade_setup_log_handlers (void); + +G_END_DECLS + +#endif /* __GLADE_DEBUG_H__ */ diff --git a/gladeui/glade-design-layout.c b/gladeui/glade-design-layout.c new file mode 100644 index 0000000..5fec0d6 --- /dev/null +++ b/gladeui/glade-design-layout.c @@ -0,0 +1,2520 @@ +/* + * glade-design-layout.c + * + * Copyright (C) 2006-2007 Vincent Geddes + * 2011-2013 Juan Pablo Ugarte + * + * Authors: + * Vincent Geddes + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" + +#include "glade.h" +#include "glade-private.h" +#include "glade-design-layout.h" +#include "glade-design-private.h" +#include "glade-accumulators.h" +#include "glade-marshallers.h" + +#include +#include + +#define OUTLINE_WIDTH 4 +#define PADDING 12 + +#define MARGIN_STEP 6 + +typedef enum +{ + ACTIVITY_NONE, + ACTIVITY_RESIZE_WIDTH, + ACTIVITY_RESIZE_HEIGHT, + ACTIVITY_RESIZE_WIDTH_AND_HEIGHT, + ACTIVITY_ALIGNMENTS, + ACTIVITY_MARGINS, + ACTIVITY_MARGINS_VERTICAL, /* These activities are only used to set the cursor */ + ACTIVITY_MARGINS_HORIZONTAL, + ACTIVITY_MARGINS_TOP_LEFT, + ACTIVITY_MARGINS_TOP_RIGHT, + ACTIVITY_MARGINS_BOTTOM_LEFT, + ACTIVITY_MARGINS_BOTTOM_RIGHT, + N_ACTIVITY +} Activity; + + +typedef enum +{ + MARGIN_TOP = 1 << 0, + MARGIN_BOTTOM = 1 << 1, + MARGIN_LEFT = 1 << 2, + MARGIN_RIGHT = 1 << 3 +} Margins; + +typedef struct _GladeDesignLayoutPrivate +{ + GladeWidget *gchild; + GdkWindow *window, *offscreen_window; + + gint child_offset; + GdkRectangle east, south, south_east; + GdkCursor *cursors[N_ACTIVITY]; + + GdkRectangle child_rect; + PangoLayout *widget_name; + gint layout_width; + + GtkStyleContext *default_context; + + /* Colors */ + GdkRGBA fg_color; + GdkRGBA frame_color[2]; + GdkRGBA frame_color_active[2]; + + /* Margin edit mode */ + GtkWidget *selection; + gint top, bottom, left, right; + gint m_dy, m_dx; + gint max_width, max_height; + Margins margin; + GtkAlign valign, halign; + Margins node_over; + + /* state machine */ + Activity activity; /* the current activity */ + gint dx; /* child.width - event.pointer.x */ + gint dy; /* child.height - event.pointer.y */ + gint new_width; /* user's new requested width */ + gint new_height; /* user's new requested height */ + + /* Drag & Drop */ + GtkWidget *drag_source; + gint drag_x, drag_y; + GladeWidget *drag_dest; + + /* Properties */ + GladeDesignView *view; + GladeProject *project; +} GladeDesignLayoutPrivate; + +enum +{ + PROP_0, + PROP_DESIGN_VIEW +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeDesignLayout, glade_design_layout, GTK_TYPE_BIN) + +#define RECTANGLE_POINT_IN(rect,x,y) (x >= rect.x && x <= (rect.x + rect.width) && y >= rect.y && y <= (rect.y + rect.height)) + +static inline gint +get_margin_left (GtkWidget *widget) +{ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + return gtk_widget_get_margin_left (widget); +G_GNUC_END_IGNORE_DEPRECATIONS +} + +static inline gint +get_margin_right (GtkWidget *widget) +{ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + return gtk_widget_get_margin_right (widget); +G_GNUC_END_IGNORE_DEPRECATIONS +} + +static inline gint +get_margin_top (GtkWidget *widget) +{ + return gtk_widget_get_margin_top (widget); +} + +static inline gint +get_margin_bottom (GtkWidget *widget) +{ + return gtk_widget_get_margin_bottom (widget); +} + +static inline void +get_margins (GtkWidget *widget, gint *l, gint *r, gint *t, gint *b) +{ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + *l = gtk_widget_get_margin_left (widget); + *r = gtk_widget_get_margin_right (widget); +G_GNUC_END_IGNORE_DEPRECATIONS + *t = gtk_widget_get_margin_top (widget); + *b = gtk_widget_get_margin_bottom (widget); +} + +static Margins +gdl_get_margins_from_pointer (GladeDesignLayout *layout, GtkWidget *widget, gint x, gint y) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + gint width, height, xx, yy, top, bottom, left, right; + GdkRectangle rec, child_rec; + Margins margin = 0; + + width = gtk_widget_get_allocated_width (widget); + height = gtk_widget_get_allocated_height (widget); + + gtk_widget_translate_coordinates (widget, GTK_WIDGET (layout), 0, 0, &xx, &yy); + + get_margins (widget, &left, &right, &top, &bottom); + + rec.x = xx - left - OUTLINE_WIDTH; + rec.y = yy - top - OUTLINE_WIDTH; + rec.width = width + left + right + (OUTLINE_WIDTH * 2); + rec.height = height + top + bottom + (OUTLINE_WIDTH * 2); + + gtk_widget_get_allocation (gtk_bin_get_child (GTK_BIN (layout)), &child_rec); + child_rec.x = (child_rec.x + priv->child_offset) - OUTLINE_WIDTH; + child_rec.y = (child_rec.y + priv->child_offset) - OUTLINE_WIDTH; + child_rec.width += OUTLINE_WIDTH * 2; + child_rec.height += OUTLINE_WIDTH * 2; + + gdk_rectangle_intersect (&rec, &child_rec, &rec); + + if (RECTANGLE_POINT_IN (rec, x, y)) + { + if (y <= yy + OUTLINE_WIDTH) margin |= MARGIN_TOP; + else if (y >= yy + height - OUTLINE_WIDTH) margin |= MARGIN_BOTTOM; + + if (x <= xx + OUTLINE_WIDTH) margin |= MARGIN_LEFT; + else if (x >= xx + width - OUTLINE_WIDTH) margin |= MARGIN_RIGHT; + } + + return margin; +} + +static Activity +gdl_get_activity_from_pointer (GladeDesignLayout *layout, gint x, gint y) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + if (priv->selection) + { + priv->margin = gdl_get_margins_from_pointer (layout, priv->selection, x, y); + + if (priv->margin) + { + GladePointerMode mode = glade_project_get_pointer_mode (priv->project); + return (mode == GLADE_POINTER_ALIGN_EDIT) ? ACTIVITY_ALIGNMENTS : ACTIVITY_MARGINS; + } + } + + if (RECTANGLE_POINT_IN (priv->south_east, x, y)) return ACTIVITY_RESIZE_WIDTH_AND_HEIGHT; + + if (RECTANGLE_POINT_IN (priv->east, x, y)) return ACTIVITY_RESIZE_WIDTH; + + if (RECTANGLE_POINT_IN (priv->south, x, y)) return ACTIVITY_RESIZE_HEIGHT; + + return ACTIVITY_NONE; +} + +static inline void +gdl_set_cursor (GladeDesignLayoutPrivate *priv, GdkCursor *cursor) +{ + if (cursor != gdk_window_get_cursor (priv->window)) + gdk_window_set_cursor (priv->window, cursor); +} + +static Activity +gdl_margin_get_activity (Margins margin) +{ + if (margin & MARGIN_TOP) + { + if (margin & MARGIN_LEFT) + return ACTIVITY_MARGINS_TOP_LEFT; + else if (margin & MARGIN_RIGHT) + return ACTIVITY_MARGINS_TOP_RIGHT; + else + return ACTIVITY_MARGINS_VERTICAL; + } + else if (margin & MARGIN_BOTTOM) + { + if (margin & MARGIN_LEFT) + return ACTIVITY_MARGINS_BOTTOM_LEFT; + else if (margin & MARGIN_RIGHT) + return ACTIVITY_MARGINS_BOTTOM_RIGHT; + else + return ACTIVITY_MARGINS_VERTICAL; + } + else if (margin & MARGIN_LEFT || margin & MARGIN_RIGHT) + return ACTIVITY_MARGINS_HORIZONTAL; + + return ACTIVITY_NONE; +} + +static gboolean +glade_design_layout_enter_leave_notify_event (GtkWidget *widget, GdkEventCrossing *ev) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GtkWidget *child; + + if ((child = gtk_bin_get_child (GTK_BIN (widget))) == NULL || + ev->window != gtk_widget_get_window (widget)) + return FALSE; + + if (ev->type == GDK_ENTER_NOTIFY) + { + Activity activity = priv->activity; + + if (priv->activity == ACTIVITY_MARGINS) + activity = gdl_margin_get_activity (priv->margin); + + gdl_set_cursor (priv, priv->cursors[activity]); + } + else if (priv->activity == ACTIVITY_NONE) + gdl_set_cursor (priv, NULL); + + return FALSE; +} + +static void +gdl_update_max_margins (GladeDesignLayout *layout, + GtkWidget *child, + gint width, gint height) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + gint parent_w, parent_h, layout_w, layout_h; + gint top, bottom, left, right; + GtkRequisition req; + + gtk_widget_get_preferred_size (child, &req, NULL); + + get_margins (priv->selection, &left, &right, &top, &bottom); + + priv->max_width = width - (req.width - left - right); + + parent_w = gtk_widget_get_allocated_width (GTK_WIDGET (priv->view)); + layout_w = gtk_widget_get_allocated_width (GTK_WIDGET (layout)); + + if (parent_w > layout_w) + priv->max_width += parent_w - layout_w - (PADDING - OUTLINE_WIDTH); + + priv->max_height = height - (req.height - top - bottom) ; + + parent_h = gtk_widget_get_allocated_height (GTK_WIDGET (priv->view)); + layout_h = gtk_widget_get_allocated_height (GTK_WIDGET (layout)); + if (parent_h > layout_h) + priv->max_height += parent_h - layout_h - (PADDING - OUTLINE_WIDTH); +} + +static void +glade_design_layout_update_child (GladeDesignLayout *layout, + GtkWidget *child, + GtkAllocation *allocation) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GladeWidget *gchild; + + /* Update GladeWidget metadata */ + gchild = glade_widget_get_from_gobject (child); + g_object_set (gchild, + "toplevel-width", allocation->width, + "toplevel-height", allocation->height, NULL); + + if (priv->selection) + gdl_update_max_margins (layout, child, allocation->width, allocation->height); + + gtk_widget_queue_resize (GTK_WIDGET (layout)); +} + +static inline void +gdl_alignments_invalidate (GdkWindow *window, + GtkWidget *parent, + GtkWidget *selection, + Margins nodes) +{ + cairo_region_t *region = cairo_region_create (); + cairo_rectangle_int_t rect = {0, 0, 16, 16}; + gint x1, x2, x3, y1, y2, y3; + GtkAllocation alloc; + gint x, y, w, h; + + gtk_widget_get_allocation (selection, &alloc); + + w = alloc.width; + h = alloc.height; + + gtk_widget_translate_coordinates (selection, parent, 0, 0, &x, &y); + + x1 = x - get_margin_left (selection); + x2 = x + w/2; + x3 = x + w + get_margin_right (selection); + y1 = y - get_margin_top (selection); + y2 = y + h/2; + y3 = y + h + get_margin_bottom (selection); + + /* Only invalidate node area */ + if (nodes & MARGIN_TOP) + { + rect.x = x2 - 5; + rect.y = y1 - 10; + cairo_region_union_rectangle (region, &rect); + } + if (nodes & MARGIN_BOTTOM) + { + rect.x = x2 - 8; + rect.y = y3 - 13; + cairo_region_union_rectangle (region, &rect); + } + + rect.y = y2 - 10; + if (nodes & MARGIN_LEFT) + { + rect.x = x1 - 8; + cairo_region_union_rectangle (region, &rect); + } + if (nodes & MARGIN_RIGHT) + { + rect.x = x3 - 5; + cairo_region_union_rectangle (region, &rect); + } + + gdk_window_invalidate_region (window, region, FALSE); + + cairo_region_destroy (region); +} + +static void +gdl_update_cursor_for_position (GtkWidget *widget, gint x, gint y) +{ + GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (widget); + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + Activity activity = gdl_get_activity_from_pointer (layout, x, y); + + if (priv->node_over != priv->margin && + (activity == ACTIVITY_ALIGNMENTS || + glade_project_get_pointer_mode (priv->project) == GLADE_POINTER_ALIGN_EDIT)) + { + if (priv->selection) + gdl_alignments_invalidate (priv->window, widget, priv->selection, + priv->node_over | priv->margin); + else + gdk_window_invalidate_rect (priv->window, NULL, FALSE); + + priv->node_over = priv->margin; + } + + if (activity == ACTIVITY_MARGINS) + activity = gdl_margin_get_activity (priv->margin); + + /* Only set the cursor if changed */ + gdl_set_cursor (priv, priv->cursors[activity]); +} + +static gboolean +glade_design_layout_motion_notify_event (GtkWidget *widget, GdkEventMotion *ev) +{ + GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (widget); + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GtkAllocation allocation; + GtkWidget *child; + gint x, y; + + if ((child = gtk_bin_get_child (GTK_BIN (widget))) == NULL) + return FALSE; + + x = ev->x; + y = ev->y; + + if (ev->state & GDK_BUTTON1_MASK && priv->drag_source && + gtk_drag_check_threshold (priv->drag_source, priv->drag_x, priv->drag_y, x, y)) + { + static GtkTargetList *target = NULL; + + if (target == NULL) + target = gtk_target_list_new (_glade_dnd_get_target (), 1); + + gtk_drag_begin_with_coordinates (widget, target, GDK_ACTION_MOVE | GDK_ACTION_COPY, 1, (GdkEvent*)ev, x, y); + return TRUE; + } + + gtk_widget_get_allocation (child, &allocation); + + allocation.x += priv->child_offset; + allocation.y += priv->child_offset; + + switch (priv->activity) + { + case ACTIVITY_RESIZE_WIDTH: + allocation.width = MAX (0, x - priv->dx - PADDING - OUTLINE_WIDTH); + glade_design_layout_update_child (layout, child, &allocation); + break; + case ACTIVITY_RESIZE_HEIGHT: + allocation.height = MAX (0, y - priv->dy - PADDING - OUTLINE_WIDTH); + glade_design_layout_update_child (layout, child, &allocation); + break; + case ACTIVITY_RESIZE_WIDTH_AND_HEIGHT: + allocation.height = MAX (0, y - priv->dy - PADDING - OUTLINE_WIDTH); + allocation.width = MAX (0, x - priv->dx - PADDING - OUTLINE_WIDTH); + glade_design_layout_update_child (layout, child, &allocation); + break; + case ACTIVITY_MARGINS: + { + gboolean shift = ev->state & GDK_SHIFT_MASK; + gboolean snap = ev->state & GDK_CONTROL_MASK; + GtkWidget *selection = priv->selection; + Margins margin = priv->margin; + + if (margin & MARGIN_TOP) + { + gint max_height = (shift) ? priv->max_height/2 : priv->max_height - + get_margin_bottom (selection); + gint val = MAX (0, MIN (priv->m_dy - y, max_height)); + + if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; + gtk_widget_set_margin_top (selection, val); + if (shift) gtk_widget_set_margin_bottom (selection, val); + } + else if (margin & MARGIN_BOTTOM) + { + gint max_height = (shift) ? priv->max_height/2 : priv->max_height - + get_margin_top (selection); + gint val = MAX (0, MIN (y - priv->m_dy, max_height)); + + if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; + gtk_widget_set_margin_bottom (selection, val); + if (shift) gtk_widget_set_margin_top (selection, val); + } + + if (margin & MARGIN_LEFT) + { + gint max_width = (shift) ? priv->max_width/2 : priv->max_width - + get_margin_right (selection); + gint val = MAX (0, MIN (priv->m_dx - x, max_width)); + + if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; + gtk_widget_set_margin_start (selection, val); + if (shift) gtk_widget_set_margin_end (selection, val); + } + else if (margin & MARGIN_RIGHT) + { + gint max_width = (shift) ? priv->max_width/2 : priv->max_width - + get_margin_left (selection); + gint val = MAX (0, MIN (x - priv->m_dx, max_width)); + + if (snap) val = (val/MARGIN_STEP)*MARGIN_STEP; + gtk_widget_set_margin_end (selection, val); + if (shift) gtk_widget_set_margin_start (selection, val); + } + } + break; + default: + gdl_update_cursor_for_position (widget, x, y); + break; + } + + return (priv->activity != ACTIVITY_NONE); +} + +static gboolean +glade_project_is_toplevel_active (GladeProject *project, GtkWidget *toplevel) +{ + GList *l; + + for (l = glade_project_selection_get (project); l; l = g_list_next (l)) + { + if (GTK_IS_WIDGET (l->data) && + gtk_widget_is_ancestor (l->data, toplevel)) return TRUE; + } + + return FALSE; +} + +static void +gdl_edit_mode_set_selection (GladeDesignLayout *layout, + GladePointerMode mode, + GtkWidget *selection) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + if ((selection && GTK_IS_WIDGET (selection) == FALSE) || + gtk_bin_get_child (GTK_BIN (layout)) == selection) selection = NULL; + + if (priv->selection == selection) return; + + priv->selection = selection; + + if (selection) + { + if (mode == GLADE_POINTER_MARGIN_EDIT) + { + GtkWidget *child = gtk_bin_get_child (GTK_BIN (layout)); + + /* Save initital margins to know which one where edited */ + get_margins (selection, &priv->left, &priv->right, &priv->top, &priv->bottom); + + gdl_update_max_margins (layout, child, + gtk_widget_get_allocated_width (child), + gtk_widget_get_allocated_height (child)); + } + else if (mode == GLADE_POINTER_ALIGN_EDIT) + { + priv->valign = gtk_widget_get_valign (selection); + priv->halign = gtk_widget_get_halign (selection); + } + + gdk_window_invalidate_rect (priv->window, NULL, FALSE); + } + else + { + gdl_set_cursor (priv, NULL); + } + + glade_project_set_pointer_mode (priv->project, mode); +} + +static gboolean +glade_design_layout_button_press_event (GtkWidget *widget, GdkEventButton *ev) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GtkAllocation child_allocation; + Activity activity; + GtkWidget *child; + gint x, y; + + if (ev->button != 1 || + (ev->type != GDK_BUTTON_PRESS && ev->type != GDK_2BUTTON_PRESS) || + (child = gtk_bin_get_child (GTK_BIN (widget))) == NULL) + return FALSE; + + x = ev->x; + y = ev->y; + + priv->activity = activity = gdl_get_activity_from_pointer (GLADE_DESIGN_LAYOUT (widget), x, y); + + /* Check if we are in margin edit mode */ + if (priv->selection) + { + GtkWidget *selection = priv->selection; + + switch (activity) + { + case ACTIVITY_NONE: + gdl_edit_mode_set_selection (GLADE_DESIGN_LAYOUT (widget), GLADE_POINTER_SELECT, NULL); + return FALSE; + break; + case ACTIVITY_ALIGNMENTS: + { + gboolean top, bottom, left, right; + Margins node = priv->margin; + GtkAlign valign, halign; + GladeWidget *gwidget; + + valign = gtk_widget_get_valign (selection); + halign = gtk_widget_get_halign (selection); + + if (valign == GTK_ALIGN_FILL) + top = bottom = TRUE; + else + { + top = (valign == GTK_ALIGN_START); + bottom = (valign == GTK_ALIGN_END); + } + + if (halign == GTK_ALIGN_FILL) + left = right = TRUE; + else + { + left = (halign == GTK_ALIGN_START); + right = (halign == GTK_ALIGN_END); + } + + if (node & MARGIN_TOP) + valign = (top) ? ((bottom) ? GTK_ALIGN_END : GTK_ALIGN_CENTER) : + ((bottom) ? GTK_ALIGN_FILL : GTK_ALIGN_START); + else if (node & MARGIN_BOTTOM) + valign = (bottom) ? ((top) ? GTK_ALIGN_START : GTK_ALIGN_CENTER) : + ((top) ? GTK_ALIGN_FILL : GTK_ALIGN_END); + + if (node & MARGIN_LEFT) + halign = (left) ? ((right) ? GTK_ALIGN_END : GTK_ALIGN_CENTER) : + ((right) ? GTK_ALIGN_FILL : GTK_ALIGN_START); + else if (node & MARGIN_RIGHT) + halign = (right) ? ((left) ? GTK_ALIGN_START : GTK_ALIGN_CENTER) : + ((left) ? GTK_ALIGN_FILL : GTK_ALIGN_END); + + if ((gwidget = glade_widget_get_from_gobject (selection))) + { + GladeProperty *property; + + glade_command_push_group (_("Editing alignments of %s"), + glade_widget_get_name (gwidget)); + + if (gtk_widget_get_valign (selection) != valign) + { + if ((property = glade_widget_get_property (gwidget, "valign"))) + glade_command_set_property (property, valign); + } + if (gtk_widget_get_halign (selection) != halign) + { + if ((property = glade_widget_get_property (gwidget, "halign"))) + glade_command_set_property (property, halign); + } + glade_command_pop_group (); + } + } + break; + case ACTIVITY_MARGINS: + priv->m_dx = x + ((priv->margin & MARGIN_LEFT) ? + get_margin_left (selection) : + get_margin_right (selection) * -1); + priv->m_dy = y + ((priv->margin & MARGIN_TOP) ? + get_margin_top (selection) : + get_margin_bottom (selection) * -1); + + gdl_set_cursor (priv, priv->cursors[gdl_margin_get_activity (priv->margin)]); + return TRUE; + break; + default: + gdl_set_cursor (priv, priv->cursors[priv->activity]); + break; + } + } + + gtk_widget_get_allocation (child, &child_allocation); + + priv->dx = x - (child_allocation.x + child_allocation.width + priv->child_offset); + priv->dy = y - (child_allocation.y + child_allocation.height + priv->child_offset); + + if (activity != ACTIVITY_NONE && + (!glade_project_is_toplevel_active (priv->project, child) || + ev->type == GDK_2BUTTON_PRESS)) + { + glade_project_selection_set (priv->project, G_OBJECT (child), TRUE); + } + + return (activity != ACTIVITY_NONE); +} + +static gboolean +glade_design_layout_button_release_event (GtkWidget *widget, + GdkEventButton *ev) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GtkWidget *child; + + if ((child = gtk_bin_get_child (GTK_BIN (widget))) == NULL) + return FALSE; + + /* Check if margins where edited and execute corresponding glade command */ + if (priv->selection && priv->activity == ACTIVITY_MARGINS) + { + GladeWidget *gwidget = glade_widget_get_from_gobject (priv->selection); + gint top, bottom, left, right; + GladeProperty *property; + + get_margins (priv->selection, &left, &right, &top, &bottom); + + glade_command_push_group (_("Editing margins of %s"), + glade_widget_get_name (gwidget)); + if (priv->top != top) + { + if ((property = glade_widget_get_property (gwidget, "margin-top"))) + glade_command_set_property (property, top); + } + if (priv->bottom != bottom) + { + if ((property = glade_widget_get_property (gwidget, "margin-bottom"))) + glade_command_set_property (property, bottom); + } + if (priv->left != left) + { + if ((property = glade_widget_get_property (gwidget, "margin-left"))) + glade_command_set_property (property, left); + } + if (priv->right != right) + { + if ((property = glade_widget_get_property (gwidget, "margin-right"))) + glade_command_set_property (property, right); + } + + glade_command_pop_group (); + } + else if (priv->activity == ACTIVITY_ALIGNMENTS) + { + priv->node_over = 0; + gdk_window_invalidate_rect (priv->window, NULL, FALSE); + } + + priv->activity = ACTIVITY_NONE; + gdl_update_cursor_for_position (widget, ev->x, ev->y); + + return TRUE; +} + +static void +glade_design_layout_get_preferred_height (GtkWidget *widget, + gint *minimum, gint *natural) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GtkWidget *child; + GladeWidget *gchild; + gint child_height = 0; + guint border_width = 0; + + *minimum = 0; + + child = gtk_bin_get_child (GTK_BIN (widget)); + + if (child && gtk_widget_get_visible (child)) + { + GtkRequisition req; + gint height; + + gchild = glade_widget_get_from_gobject (child); + g_assert (gchild); + + gtk_widget_get_preferred_size (child, &req, NULL); + + g_object_get (gchild, "toplevel-height", &child_height, NULL); + + child_height = MAX (child_height, req.height); + + if (priv->widget_name) + pango_layout_get_pixel_size (priv->widget_name, NULL, &height); + else + height = PADDING; + + *minimum = MAX (*minimum, PADDING + 2.5*OUTLINE_WIDTH + height + child_height); + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + *minimum += border_width * 2; + *natural = *minimum; +} + +static void +glade_design_layout_get_preferred_width (GtkWidget *widget, + gint *minimum, gint *natural) +{ + GtkWidget *child; + GladeWidget *gchild; + gint child_width = 0; + guint border_width = 0; + + *minimum = 0; + + child = gtk_bin_get_child (GTK_BIN (widget)); + + if (child && gtk_widget_get_visible (child)) + { + GtkRequisition req; + + gchild = glade_widget_get_from_gobject (child); + g_assert (gchild); + + gtk_widget_get_preferred_size (child, &req, NULL); + + g_object_get (gchild, "toplevel-width", &child_width, NULL); + + child_width = MAX (child_width, req.width); + + *minimum = MAX (*minimum, 2*PADDING + 2*OUTLINE_WIDTH + child_width); + } + + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + *minimum += border_width * 2; + *natural = *minimum; +} + +static void +glade_design_layout_get_preferred_width_for_height (GtkWidget *widget, + gint height, + gint *minimum_width, + gint *natural_width) +{ + glade_design_layout_get_preferred_width (widget, minimum_width, natural_width); +} + +static void +glade_design_layout_get_preferred_height_for_width (GtkWidget *widget, + gint width, + gint *minimum_height, + gint *natural_height) +{ + glade_design_layout_get_preferred_height (widget, minimum_height, natural_height); +} + +static void +update_rectangles (GladeDesignLayoutPrivate *priv, GtkAllocation *alloc) +{ + GdkRectangle *rect = &priv->south_east; + gint width, height; + + /* Update rectangles used to resize the children */ + priv->east.x = alloc->width + priv->child_offset; + priv->east.y = priv->child_offset; + priv->east.height = alloc->height; + + priv->south.x = priv->child_offset; + priv->south.y = alloc->height + priv->child_offset; + priv->south.width = alloc->width; + + /* Update south east rectangle width */ + if (priv->widget_name) + pango_layout_get_pixel_size (priv->widget_name, &width, &height); + else + width = height = 0; + + priv->layout_width = width + (OUTLINE_WIDTH*2); + width = MIN (alloc->width, width); + + rect->x = alloc->x + priv->child_offset + alloc->width - width - OUTLINE_WIDTH; + rect->y = alloc->y + priv->child_offset + alloc->height; + rect->width = width + (OUTLINE_WIDTH*2); + rect->height = height + (OUTLINE_WIDTH*1.5); + + /* Update south rectangle width */ + priv->south.width = rect->x - priv->south.x; +} + +static void +glade_design_layout_size_allocate (GtkWidget *widget, + GtkAllocation *allocation) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GtkWidget *child; + + gtk_widget_set_allocation (widget, allocation); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (gtk_widget_get_window (widget), + allocation->x, allocation->y, + allocation->width, allocation->height); + } + + child = gtk_bin_get_child (GTK_BIN (widget)); + + if (child && gtk_widget_get_visible (child)) + { + GtkAllocation alloc; + gint height, offset; + + offset = gtk_container_get_border_width (GTK_CONTAINER (widget)) + PADDING + OUTLINE_WIDTH; + priv->child_rect.x = priv->child_rect.y = priv->child_offset = offset; + + if (priv->widget_name) + pango_layout_get_pixel_size (priv->widget_name, NULL, &height); + else + height = PADDING; + + alloc.x = alloc.y = 0; + priv->child_rect.width = alloc.width = allocation->width - (offset * 2); + priv->child_rect.height = alloc.height = allocation->height - (offset + OUTLINE_WIDTH * 1.5 + height); + + if (gtk_widget_get_realized (widget)) + gdk_window_move_resize (priv->offscreen_window, + 0, 0, alloc.width, alloc.height); + + gtk_widget_size_allocate (child, &alloc); + update_rectangles (priv, &alloc); + } +} + +static inline void +update_widget_name (GladeDesignLayout *layout, GladeWidget *gwidget) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + if (priv->widget_name && gwidget) + { + if (glade_widget_has_name (gwidget)) + { + pango_layout_set_text (priv->widget_name, glade_widget_get_display_name (gwidget), -1); + } + else + { + GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (gwidget); + pango_layout_set_text (priv->widget_name, glade_widget_adaptor_get_display_name (adaptor), -1); + } + + gtk_widget_queue_resize (GTK_WIDGET (layout)); + } +} + +static void +on_glade_widget_name_notify (GObject *gobject, GParamSpec *pspec, GladeDesignLayout *layout) +{ + update_widget_name (layout, GLADE_WIDGET (gobject)); +} + +static void +glade_design_layout_add (GtkContainer *container, GtkWidget *widget) +{ + GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (container); + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GtkStyleContext *context = gtk_widget_get_style_context (widget); + + priv->child_rect.width = 0; + priv->child_rect.height = 0; + + gtk_style_context_add_class (context, "background"); + + gtk_widget_set_parent_window (widget, priv->offscreen_window); + + GTK_CONTAINER_CLASS (glade_design_layout_parent_class)->add (container, + widget); + + if (!priv->gchild && + (priv->gchild = glade_widget_get_from_gobject (G_OBJECT (widget)))) + { + update_widget_name (layout, priv->gchild); + g_signal_connect (priv->gchild, "notify::name", + G_CALLBACK (on_glade_widget_name_notify), + layout); + } + + gtk_widget_queue_draw (GTK_WIDGET (container)); +} + +static void +glade_design_layout_remove (GtkContainer *container, GtkWidget *widget) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) container); + GladeWidget *gchild; + + if ((gchild = glade_widget_get_from_gobject (G_OBJECT (widget)))) + { + g_signal_handlers_disconnect_by_func (gchild, on_glade_widget_name_notify, + GLADE_DESIGN_LAYOUT (container)); + if (gchild == priv->gchild) + priv->gchild = NULL; + } + + GTK_CONTAINER_CLASS (glade_design_layout_parent_class)->remove (container, widget); + gtk_widget_queue_draw (GTK_WIDGET (container)); +} + +static gboolean +glade_design_layout_damage (GtkWidget *widget, GdkEventExpose *event) +{ + gdk_window_invalidate_rect (gtk_widget_get_window (widget), NULL, TRUE); + return TRUE; +} + +static inline void +draw_frame (GtkWidget *widget, cairo_t *cr, gboolean selected, + int x, int y, int w, int h) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GtkStyleContext *context = gtk_widget_get_style_context (widget); + + gtk_render_background (context, cr, x, y, w, h); + gtk_render_frame (context, cr, x, y, w, h); + + if (priv->widget_name) + { + GdkRectangle *rect = &priv->south_east; + gtk_style_context_save (context); + gtk_style_context_add_class (context, "handle"); + gtk_render_background (context, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_frame (context, cr, rect->x, rect->y, rect->width, rect->height); + gtk_render_layout (context, cr, rect->x + OUTLINE_WIDTH, rect->y + OUTLINE_WIDTH, + priv->widget_name); + gtk_style_context_restore (context); + } +} + +static void +draw_margin_selection (cairo_t *cr, + gint x1, gint x2, gint x3, gint x4, + gint y1, gint y2, gint y3, gint y4, + gdouble r, gdouble g, gdouble b, + gint x5, gint y5) +{ + cairo_pattern_t *gradient = cairo_pattern_create_linear (x1, y1, x5, y5); + + cairo_pattern_add_color_stop_rgba (gradient, 0, r+.24, g+.24, b+.24, .08); + cairo_pattern_add_color_stop_rgba (gradient, 1, r, g, b, .16); + + cairo_set_source (cr, gradient); + + cairo_move_to (cr, x1, y1); + cairo_line_to (cr, x2, y2); + cairo_line_to (cr, x3, y3); + cairo_line_to (cr, x4, y4); + cairo_close_path (cr); + cairo_fill (cr); + + cairo_pattern_destroy (gradient); +} + +static inline void +draw_selection (cairo_t *cr, + GtkWidget *parent, + GtkWidget *widget, + GdkRGBA *color) +{ + gint x, y, w, h, xw, yh, y_top, yh_bottom, x_left, xw_right; + gint top, bottom, left, right; + gdouble r, g, b; + GtkAllocation alloc; + GtkStyleContext *context; + + gtk_widget_get_allocation (widget, &alloc); + + if (alloc.x < 0 || alloc.y < 0) return; + + context = gtk_widget_get_style_context (parent); + gtk_style_context_save (context); + gtk_style_context_add_class (context, "selection"); + r = color->red; g = color->green; b = color->blue; + gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y); + + w = alloc.width; + h = alloc.height; + xw = x + w; + yh = y + h; + + get_margins (widget, &left, &right, &top, &bottom); + + y_top = y - top; + yh_bottom = yh + bottom; + x_left = x - left; + xw_right = xw + right; + + /* Draw widget area overlay */ + gtk_render_background (context, cr, x, y, w, h); + + /* Draw margins overlays */ + if (top) + draw_margin_selection (cr, x, xw, xw_right, x_left, y, y, y_top, y_top, + r, g, b, x, y_top); + + if (bottom) + draw_margin_selection (cr, x, xw, xw_right, x_left, yh, yh, yh_bottom, yh_bottom, + r, g, b, x, yh_bottom); + + if (left) + draw_margin_selection (cr, x, x, x_left, x_left, y, yh, yh_bottom, y_top, + r, g, b, x_left, y); + + if (right) + draw_margin_selection (cr, xw, xw, xw_right, xw_right, y, yh, yh_bottom, y_top, + r, g, b, xw_right, y); + + /* Draw Selection box */ + gtk_render_frame (context, cr, x - left, y - top, w + left + right, h + top + bottom); + gtk_style_context_restore (context); +} + +#define DIMENSION_OFFSET 9 +#define DIMENSION_LINE_OFFSET 4 + +static void +draw_hmark (cairo_t *cr, gdouble x, gdouble y) +{ + cairo_move_to (cr, x + 2, y - 2); + cairo_line_to (cr, x - 2, y + 2); +} + +static void +draw_vmark (cairo_t *cr, gdouble x, gdouble y) +{ + cairo_move_to (cr, x - 2, y - 2); + cairo_line_to (cr, x + 2, y + 2); +} + +static void +draw_vguide (cairo_t *cr, gdouble x, gdouble y, gint len) +{ + cairo_move_to (cr, x, y - DIMENSION_LINE_OFFSET); + cairo_line_to (cr, x, y + len); +} + +static void +draw_hguide (cairo_t *cr, gdouble x, gdouble y, gint len) +{ + cairo_move_to (cr, x + DIMENSION_LINE_OFFSET, y); + cairo_line_to (cr, x - len, y); +} + +static void +draw_pixel_value (cairo_t *cr, + GdkRGBA *bg, GdkRGBA *fg, + gdouble x, gdouble y, + gboolean rotate, + gboolean draw_border, + gint val) +{ + cairo_text_extents_t extents; + gchar pixel_str[8]; + + g_snprintf (pixel_str, 8, "%d", val); + + cairo_text_extents (cr, pixel_str, &extents); + + cairo_save (cr); + + if (rotate) + { + cairo_translate (cr, x - 1.5, y + .5 + extents.width/2); + cairo_rotate (cr, G_PI/-2); + } + else + cairo_translate (cr, x - (extents.width+extents.x_bearing)/2, y - 2); + + cairo_move_to (cr, 0, 0); + + if (draw_border || extents.width + 4 >= val) + { + cairo_set_source_rgba (cr, bg->red, bg->green, bg->blue, .9); + + cairo_text_path (cr, pixel_str); + cairo_set_line_width (cr, 3); + cairo_stroke (cr); + + cairo_set_line_width (cr, 1); + gdk_cairo_set_source_rgba (cr, fg); + } + + cairo_show_text (cr, pixel_str); + + cairo_restore (cr); +} + +static void +draw_stroke_lines (cairo_t *cr, GdkRGBA *bg, GdkRGBA *fg, gboolean remark) +{ + if (remark) + { + cairo_set_source_rgba (cr, bg->red, bg->green, bg->blue, .9); + cairo_set_line_width (cr, 3); + cairo_stroke_preserve (cr); + cairo_set_line_width (cr, 1); + } + + gdk_cairo_set_source_rgba (cr, fg); + cairo_stroke (cr); +} + +static void +draw_dimensions (cairo_t *cr, + GdkRGBA *bg, GdkRGBA *fg, + gdouble x, gdouble y, + gint w, gint h, + gint top, gint bottom, + gint left, gint right) +{ + gboolean h_clutter, v_clutter; + gdouble xx, yy; + GdkRGBA color; + + w--; h--; + xx = x + w + DIMENSION_OFFSET; + yy = y - DIMENSION_OFFSET; + h_clutter = top < DIMENSION_OFFSET*2; + v_clutter = right < (DIMENSION_OFFSET + OUTLINE_WIDTH); + + /* Color half way betwen fg and bg */ + color.red = ABS (bg->red - fg->red)/2; + color.green = ABS (bg->green - fg->green)/2; + color.blue = ABS (bg->blue - fg->blue)/2; + color.alpha = fg->alpha; + + cairo_set_font_size (cr, 8.0); + + /* Draw dimension lines and guides */ + if (left || right) + { + /* Draw horizontal lines */ + cairo_move_to (cr, x - left - DIMENSION_LINE_OFFSET, yy); + cairo_line_to (cr, x + w + right + DIMENSION_LINE_OFFSET, yy); + + if (top < DIMENSION_OFFSET) + { + draw_vguide (cr, x - left, yy, DIMENSION_OFFSET - top); + draw_vguide (cr, x + w + right, yy, DIMENSION_OFFSET - top); + } + + draw_vguide (cr, x, yy, DIMENSION_OFFSET); + draw_vguide (cr, x + w, yy, DIMENSION_OFFSET); + + draw_stroke_lines (cr, bg, &color, top < DIMENSION_OFFSET+OUTLINE_WIDTH); + + /* Draw dimension line marks */ + if (left) draw_hmark (cr, x - left, yy); + draw_hmark (cr, x, yy); + draw_hmark (cr, x + w, yy); + if (right) draw_hmark (cr, x + w + right, yy); + + draw_stroke_lines (cr, bg, fg, top < DIMENSION_OFFSET+OUTLINE_WIDTH); + } + + if (top || bottom) + { + /* Draw vertical lines */ + cairo_move_to (cr, xx, y - top - DIMENSION_LINE_OFFSET); + cairo_line_to (cr, xx, y + h + bottom + DIMENSION_LINE_OFFSET); + + if (right < DIMENSION_OFFSET) + { + draw_hguide (cr, xx, y - top, DIMENSION_OFFSET - right); + draw_hguide (cr, xx, y + h + bottom, DIMENSION_OFFSET - right); + } + + draw_hguide (cr, xx, y, DIMENSION_OFFSET); + draw_hguide (cr, xx, y + h, DIMENSION_OFFSET); + + draw_stroke_lines (cr, bg, &color, v_clutter); + + /* Draw marks */ + if (top) draw_vmark (cr, xx, y - top); + draw_vmark (cr, xx, y); + draw_vmark (cr, xx, y + h); + if (bottom) draw_vmark (cr, xx, y + h + bottom); + + draw_stroke_lines (cr, bg, fg, v_clutter); + } + + if (left || right) + { + /* Draw pixel values */ + draw_pixel_value (cr, bg, fg, x + w/2, yy, FALSE, h_clutter, w+1); + if (left) draw_pixel_value (cr,bg, fg, x - left/2, yy, FALSE, h_clutter, left); + if (right) draw_pixel_value (cr,bg, fg, x + w + right/2, yy, FALSE, h_clutter, right); + } + + if (top || bottom) + { + /* Draw pixel values */ + draw_pixel_value (cr,bg, fg, xx, y + h/2, TRUE, v_clutter, h+1); + if (top) draw_pixel_value (cr,bg, fg, xx, y - top/2, TRUE, v_clutter, top); + if (bottom) draw_pixel_value (cr,bg, fg, xx, y + h + bottom/2, TRUE, v_clutter, bottom); + } +} + +static void +draw_pushpin (cairo_t *cr, gdouble x, gdouble y, gint angle, + GdkRGBA *outline, GdkRGBA *fill, GdkRGBA *outline2, GdkRGBA *fg, + gboolean over, gboolean active) +{ + cairo_save (cr); + + if (active) + { + outline = outline2; + cairo_translate (cr, x + .5, y); + cairo_rotate (cr, angle*(G_PI/180)); + } + else + cairo_translate (cr, x + 1.5, y); + + /* Swap colors if mouse is over */ + if (over) + { + GdkRGBA *tmp = outline; + outline = fill; + fill = tmp; + } + + cairo_move_to (cr, 0, 0); + _glade_design_layout_draw_pushpin (cr, (active) ? 2.5 : 4, outline, fill, + (over) ? outline : fill, fg); + + cairo_restore (cr); +} + +static inline void +draw_selection_nodes (cairo_t *cr, + GladeDesignLayoutPrivate *priv, + GtkWidget *parent) +{ + GladePointerMode mode = glade_project_get_pointer_mode (priv->project); + Margins node = priv->node_over; + GtkWidget *widget = priv->selection; + gint top, bottom, left, right; + gint x1, x2, x3, y1, y2, y3; + GtkAllocation alloc; + GdkRGBA *c1, *c2, *c3, *fg; + gint x, y, w, h; + gint offset = priv->child_offset; + GdkRectangle clip = {0, }; + + gtk_widget_translate_coordinates (widget, parent, 0, 0, &x, &y); + + c1 = &priv->frame_color_active[0]; + c2 = &priv->frame_color_active[1]; + c3 = &priv->frame_color[0]; + fg = &priv->fg_color; + + gtk_widget_get_allocation (widget, &alloc); + w = alloc.width; + h = alloc.height; + + get_margins (widget, &left, &right, &top, &bottom); + + /* Draw nodes */ + x1 = x - left; + x2 = x + w/2; + x3 = x + w + right; + y1 = y - top; + y2 = y + h/2; + y3 = y + h + bottom; + + /* Calculate clipping region to avoid drawing nodes outside of the toplevel + * allocation. This could happen for example with GtkOverlay overlay children. + */ + if (offset > x1) + { + clip.x = offset; + clip.width = 0; + } + else + { + clip.x = 0; + clip.width = offset; + } + if (offset > y1) + { + clip.y = offset; + clip.height = 0; + } + else + { + clip.y = 0; + clip.height = offset; + } + + clip.width += (priv->child_rect.width + offset < x3) ? + priv->child_rect.width : gdk_window_get_width (priv->window); + clip.height += (priv->child_rect.height + offset < y3) ? + priv->child_rect.height : gdk_window_get_height (priv->window); + + cairo_save (cr); + gdk_cairo_rectangle (cr, &clip); + cairo_clip (cr); + + /* Draw nodes */ + cairo_set_line_width (cr, OUTLINE_WIDTH); + + if (mode == GLADE_POINTER_MARGIN_EDIT) + { + _glade_design_layout_draw_node (cr, x2, y1, c1, c2); + _glade_design_layout_draw_node (cr, x2, y3, c1, c2); + _glade_design_layout_draw_node (cr, x1, y2, c1, c2); + _glade_design_layout_draw_node (cr, x3, y2, c1, c2); + + /* Draw dimensions */ + if (top || bottom || left || right) + { + cairo_set_line_width (cr, 1); + draw_dimensions (cr, c2, fg, x+.5, y+.5, w, h, top, bottom, left, right); + } + } + else if (mode == GLADE_POINTER_ALIGN_EDIT) + { + GtkAlign valign, halign; + + valign = gtk_widget_get_valign (widget); + halign = gtk_widget_get_halign (widget); + + if (valign == GTK_ALIGN_FILL) + { + draw_pushpin (cr, x2, y1, 45, c3, c2, c1, fg, node & MARGIN_TOP, TRUE); + draw_pushpin (cr, x2, y3-4, -45, c3, c2, c1, fg, node & MARGIN_BOTTOM, TRUE); + } + else + { + draw_pushpin (cr, x2, y1, 45, c3, c2, c1, fg, node & MARGIN_TOP, valign == GTK_ALIGN_START); + draw_pushpin (cr, x2, y3-4, -45, c3, c2, c1, fg, node & MARGIN_BOTTOM, valign == GTK_ALIGN_END); + } + + if (halign == GTK_ALIGN_FILL) + { + draw_pushpin (cr, x1, y2, -45, c3, c2, c1, fg, node & MARGIN_LEFT, TRUE); + draw_pushpin (cr, x3, y2, 45, c3, c2, c1, fg, node & MARGIN_RIGHT, TRUE); + } + else + { + draw_pushpin (cr, x1, y2, -45, c3, c2, c1, fg, node & MARGIN_LEFT, halign == GTK_ALIGN_START); + draw_pushpin (cr, x3, y2, 45, c3, c2, c1, fg, node & MARGIN_RIGHT, halign == GTK_ALIGN_END); + } + } + + cairo_restore (cr); +} + +static inline void +draw_drag_dest (GladeDesignLayout *layout, cairo_t *cr, GladeWidget *drag) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GObject *obj = glade_widget_get_object (drag); + const double dashes = 3; + GtkWidget *widget; + gint x, y; + + if (!GTK_IS_WIDGET (obj)) + return; + + widget = GTK_WIDGET (obj); + + gtk_widget_translate_coordinates (widget, GTK_WIDGET (layout), 0, 0, &x, &y); + cairo_set_line_width (cr, 2); + cairo_set_dash (cr, &dashes, 1, 0); + gdk_cairo_set_source_rgba (cr, &priv->frame_color_active[0]); + cairo_rectangle (cr, x+1, y+1, + gtk_widget_get_allocated_width (widget)-2, + gtk_widget_get_allocated_height(widget)-2); + cairo_stroke (cr); +} + +static gboolean +glade_design_layout_draw (GtkWidget *widget, cairo_t *cr) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GdkWindow *window = gtk_widget_get_window (widget); + + if (gtk_cairo_should_draw_window (cr, window)) + { + GtkWidget *child; + + if ((child = gtk_bin_get_child (GTK_BIN (widget))) && + gtk_widget_get_visible (child)) + { + gint border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + gboolean selected = FALSE; + GList *l; + + /* draw frame */ + draw_frame (widget, cr, selected, + border_width + PADDING, + border_width + PADDING, + priv->child_rect.width + 2 * OUTLINE_WIDTH, + priv->child_rect.height + 2 * OUTLINE_WIDTH); + + /* draw offscreen widgets */ + gdk_cairo_set_source_window (cr, priv->offscreen_window, + priv->child_offset, priv->child_offset); + gdk_cairo_rectangle (cr, &priv->child_rect); + cairo_fill (cr); + + /* Draw selection */ + cairo_set_line_width (cr, OUTLINE_WIDTH/2); + cairo_set_line_join (cr, CAIRO_LINE_JOIN_ROUND); + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + for (l = glade_project_selection_get (priv->project); l; l = g_list_next (l)) + { + GtkWidget *selection = l->data; + + /* Dont draw selection on toplevels */ + if (child != selection) + { + if (GTK_IS_WIDGET (selection) && + gtk_widget_is_ancestor (selection, child)) + { + GdkRectangle *rect = &priv->child_rect; + cairo_save (cr); + cairo_rectangle (cr, + rect->x - OUTLINE_WIDTH/2, + rect->y - OUTLINE_WIDTH/2, + rect->width + OUTLINE_WIDTH, + rect->height + OUTLINE_WIDTH); + cairo_clip (cr); + draw_selection (cr, widget, selection, &priv->frame_color_active[0]); + cairo_restore (cr); + + selected = TRUE; + } + } + else + selected = TRUE; + } + + /* Draw selection nodes if we are in margins edit mode */ + if (priv->selection && gtk_widget_is_ancestor (priv->selection, child)) + draw_selection_nodes (cr, priv, widget); + + if (priv->drag_dest) + draw_drag_dest (GLADE_DESIGN_LAYOUT (widget), cr, priv->drag_dest); + } + } + else if (gtk_cairo_should_draw_window (cr, priv->offscreen_window)) + { + GtkWidget *child = gtk_bin_get_child (GTK_BIN (widget)); + + gtk_render_background (priv->default_context, cr, 0, 0, + gdk_window_get_width (priv->offscreen_window), + gdk_window_get_height (priv->offscreen_window)); + + if (child) + gtk_container_propagate_draw (GTK_CONTAINER (widget), child, cr); + } + + return FALSE; +} + +static inline void +to_child (GladeDesignLayout *bin, + double widget_x, + double widget_y, + double *x_out, + double *y_out) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (bin); + *x_out = widget_x - priv->child_offset; + *y_out = widget_y - priv->child_offset; +} + +static inline void +to_parent (GladeDesignLayout *bin, + double offscreen_x, + double offscreen_y, + double *x_out, + double *y_out) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (bin); + *x_out = offscreen_x + priv->child_offset; + *y_out = offscreen_y + priv->child_offset; +} + +static GdkWindow * +pick_offscreen_child (GdkWindow *offscreen_window, + double widget_x, + double widget_y, + GladeDesignLayout *bin) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (bin); + GtkWidget *child = gtk_bin_get_child (GTK_BIN (bin)); + + if (child && gtk_widget_get_visible (child)) + { + GtkAllocation child_area; + double x, y; + + to_child (bin, widget_x, widget_y, &x, &y); + + gtk_widget_get_allocation (child, &child_area); + + if (x >= 0 && x < child_area.width && y >= 0 && y < child_area.height) + return (priv->selection) ? NULL : priv->offscreen_window; + } + + return NULL; +} + +static void +offscreen_window_to_parent (GdkWindow *offscreen_window, + double offscreen_x, + double offscreen_y, + double *parent_x, + double *parent_y, + GladeDesignLayout *bin) +{ + to_parent (bin, offscreen_x, offscreen_y, parent_x, parent_y); +} + +static void +offscreen_window_from_parent (GdkWindow *window, + double parent_x, + double parent_y, + double *offscreen_x, + double *offscreen_y, + GladeDesignLayout *bin) +{ + to_child (bin, parent_x, parent_y, offscreen_x, offscreen_y); +} + +static void +glade_design_layout_realize (GtkWidget *widget) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GdkWindowAttr attributes; + GtkWidget *child; + gint attributes_mask, border_width; + GtkAllocation allocation; + GdkDisplay *display; + + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + border_width = gtk_container_get_border_width (GTK_CONTAINER (widget)); + + attributes.x = allocation.x + border_width; + attributes.y = allocation.y + border_width; + attributes.width = allocation.width - 2 * border_width; + attributes.height = allocation.height - 2 * border_width; + attributes.window_type = GDK_WINDOW_CHILD; + attributes.event_mask = gtk_widget_get_events (widget) + | GDK_EXPOSURE_MASK + | GDK_POINTER_MOTION_MASK + | GDK_BUTTON_PRESS_MASK + | GDK_BUTTON_RELEASE_MASK + | GDK_SCROLL_MASK + | GDK_ENTER_NOTIFY_MASK + | GDK_LEAVE_NOTIFY_MASK; + + attributes.visual = gtk_widget_get_visual (widget); + attributes.wclass = GDK_INPUT_OUTPUT; + + attributes_mask = GDK_WA_X | GDK_WA_Y | GDK_WA_VISUAL; + + priv->window = gdk_window_new (gtk_widget_get_parent_window (widget), + &attributes, attributes_mask); + gtk_widget_set_window (widget, priv->window); + gdk_window_set_user_data (priv->window, widget); + + g_signal_connect (priv->window, "pick-embedded-child", + G_CALLBACK (pick_offscreen_child), widget); + + /* Offscreen window */ + child = gtk_bin_get_child (GTK_BIN (widget)); + attributes.window_type = GDK_WINDOW_OFFSCREEN; + attributes.x = attributes.y = 0; + + if (child && gtk_widget_get_visible (child)) + { + GtkAllocation alloc; + + gtk_widget_get_allocation (child, &alloc); + attributes.width = alloc.width; + attributes.height = alloc.height; + } + else + attributes.width = attributes.height = 0; + + priv->offscreen_window = gdk_window_new (NULL, &attributes, attributes_mask); + gdk_window_set_user_data (priv->offscreen_window, widget); + + if (child) gtk_widget_set_parent_window (child, priv->offscreen_window); + gdk_offscreen_window_set_embedder (priv->offscreen_window, priv->window); + + g_signal_connect (priv->offscreen_window, "to-embedder", + G_CALLBACK (offscreen_window_to_parent), widget); + g_signal_connect (priv->offscreen_window, "from-embedder", + G_CALLBACK (offscreen_window_from_parent), widget); + + gdk_window_show (priv->offscreen_window); + + gdk_window_set_cursor (priv->window, NULL); + gdk_window_set_cursor (priv->offscreen_window, NULL); + + /* Allocate cursors */ + display = gtk_widget_get_display (widget); + priv->cursors[ACTIVITY_RESIZE_HEIGHT] = gdk_cursor_new_for_display (display, GDK_BOTTOM_SIDE); + priv->cursors[ACTIVITY_RESIZE_WIDTH] = gdk_cursor_new_for_display (display, GDK_RIGHT_SIDE); + priv->cursors[ACTIVITY_RESIZE_WIDTH_AND_HEIGHT] = gdk_cursor_new_for_display (display, GDK_BOTTOM_RIGHT_CORNER); + + priv->cursors[ACTIVITY_MARGINS_VERTICAL] = gdk_cursor_new_for_display (display, GDK_SB_V_DOUBLE_ARROW); + priv->cursors[ACTIVITY_MARGINS_HORIZONTAL] = gdk_cursor_new_for_display (display, GDK_SB_H_DOUBLE_ARROW); + priv->cursors[ACTIVITY_MARGINS_TOP_LEFT] = gdk_cursor_new_for_display (display, GDK_TOP_LEFT_CORNER); + priv->cursors[ACTIVITY_MARGINS_TOP_RIGHT] = gdk_cursor_new_for_display (display, GDK_TOP_RIGHT_CORNER); + priv->cursors[ACTIVITY_MARGINS_BOTTOM_LEFT] = gdk_cursor_new_for_display (display, GDK_BOTTOM_LEFT_CORNER); + priv->cursors[ACTIVITY_MARGINS_BOTTOM_RIGHT] = g_object_ref (priv->cursors[ACTIVITY_RESIZE_WIDTH_AND_HEIGHT]); + + priv->widget_name = pango_layout_new (gtk_widget_get_pango_context (widget)); + if (child) + update_widget_name (GLADE_DESIGN_LAYOUT (widget), + glade_widget_get_from_gobject (child)); +} + +static void +glade_design_layout_unrealize (GtkWidget *widget) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + gint i; + + if (priv->offscreen_window) + { + gdk_window_set_user_data (priv->offscreen_window, NULL); + gdk_window_destroy (priv->offscreen_window); + priv->offscreen_window = NULL; + } + + /* Free cursors */ + for (i = 0; i < N_ACTIVITY; i++) + { + if (priv->cursors[i]) + { + g_object_unref (priv->cursors[i]); + priv->cursors[i] = NULL; + } + } + + if (priv->widget_name) + { + g_object_unref (priv->widget_name); + priv->widget_name = NULL; + } + + GTK_WIDGET_CLASS (glade_design_layout_parent_class)->unrealize (widget); +} + +static void +glade_design_layout_style_updated (GtkWidget *widget) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + + _glade_design_layout_get_colors (&priv->frame_color[0], + &priv->frame_color[1], + &priv->frame_color_active[0], + &priv->frame_color_active[1]); + + priv->fg_color = priv->frame_color[1]; +} + +static void +glade_design_layout_init (GladeDesignLayout *layout) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GtkWidgetPath *path = gtk_widget_path_new (); + gint i; + + priv->activity = ACTIVITY_NONE; + + for (i = 0; i < N_ACTIVITY; i++) priv->cursors[i] = NULL; + + priv->new_width = -1; + priv->new_height = -1; + priv->node_over = 0; + + priv->default_context = gtk_style_context_new (); + gtk_widget_path_append_type (path, GTK_TYPE_WINDOW); + gtk_style_context_set_path (priv->default_context, path); + gtk_style_context_add_class (priv->default_context, GTK_STYLE_CLASS_BACKGROUND); + + /* setup static member of rectangles */ + priv->east.width = PADDING + OUTLINE_WIDTH; + priv->south.height = PADDING + OUTLINE_WIDTH; + + gtk_widget_set_has_window (GTK_WIDGET (layout), TRUE); + + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (layout)), + GTK_STYLE_CLASS_VIEW); + + gtk_widget_path_unref (path); +} + +static void +on_pointer_mode_notify (GladeProject *project, + GParamSpec *pspec, + GladeDesignLayout *layout) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GladePointerMode mode; + GtkWidget *selection; + + g_return_if_fail (priv->window); + + mode = glade_project_get_pointer_mode (priv->project); + if (mode == GLADE_POINTER_MARGIN_EDIT || mode == GLADE_POINTER_ALIGN_EDIT) + { + GList *l = glade_project_selection_get (project); + selection = (l && g_list_next (l) == NULL && GTK_IS_WIDGET (l->data)) ? l->data : NULL; + gdl_edit_mode_set_selection (layout, mode, NULL); + } + else + selection = NULL; + + gdl_edit_mode_set_selection (layout, mode, selection); + gdk_window_invalidate_rect (priv->window, NULL, FALSE); +} + +static void +glade_design_layout_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) object); + + switch (prop_id) + { + case PROP_DESIGN_VIEW: + priv->view = GLADE_DESIGN_VIEW (g_value_get_object (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_design_layout_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) object); + + switch (prop_id) + { + case PROP_DESIGN_VIEW: + g_value_set_object (value, priv->view); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +on_project_selection_changed (GladeProject *project, GladeDesignLayout *layout) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GladePointerMode mode = glade_project_get_pointer_mode (project); + + if (priv->selection) + gdl_edit_mode_set_selection (layout, GLADE_POINTER_SELECT, NULL); + else if (mode == GLADE_POINTER_ALIGN_EDIT || mode == GLADE_POINTER_MARGIN_EDIT) + { + GList *l = glade_project_selection_get (project); + gdl_edit_mode_set_selection (layout, mode, (l) ? l->data : NULL); + } +} + +static GObject * +glade_design_layout_constructor (GType type, + guint n_construct_params, + GObjectConstructParam *construct_params) +{ + GladeDesignLayout *self; + GladeDesignLayoutPrivate *priv; + GObject *object; + + object = G_OBJECT_CLASS (glade_design_layout_parent_class)->constructor (type, + n_construct_params, + construct_params); + + self = GLADE_DESIGN_LAYOUT (object); + priv = glade_design_layout_get_instance_private (self); + + /* Keep a weak reference to project */ + priv->project = glade_design_view_get_project (priv->view); + g_object_add_weak_pointer (G_OBJECT (priv->project), (gpointer *) &priv->project); + + g_signal_connect (priv->project, "notify::pointer-mode", + G_CALLBACK (on_pointer_mode_notify), + self); + + g_signal_connect (priv->project, "selection-changed", + G_CALLBACK (on_project_selection_changed), + self); + + glade_design_layout_style_updated (GTK_WIDGET (object)); + + return object; +} + +static void +glade_design_layout_dispose (GObject *object) +{ + GtkWidget *child; + + /* NOTE: Remove child from hierarchy! + * This way we ensure gtk_widget_destroy() will not be called on it. + * Glade API like glade_widget_get_children() depends on children hierarchy. + */ + if ((child = gtk_bin_get_child (GTK_BIN (object)))) + gtk_container_remove (GTK_CONTAINER (object), child); + + G_OBJECT_CLASS (glade_design_layout_parent_class)->dispose (object); +} + +static void +glade_design_layout_finalize (GObject *object) +{ + GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (object); + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + g_clear_object (&priv->default_context); + g_clear_object (&priv->drag_dest); + + if (priv->project) + { + g_signal_handlers_disconnect_by_func (priv->project, + on_project_selection_changed, + layout); + g_signal_handlers_disconnect_by_func (priv->project, + on_pointer_mode_notify, + layout); + + priv->project = NULL; + } + + G_OBJECT_CLASS (glade_design_layout_parent_class)->finalize (object); +} + +static void +glade_design_layout_drag_begin (GtkWidget *widget, GdkDragContext *context) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + GladeWidgetAdaptor *adaptor; + GladeWidget *gwidget; + gchar *description; + + gwidget = glade_widget_get_from_gobject (priv->drag_source); + adaptor = glade_widget_get_adaptor (gwidget); + description = g_strdup_printf ("%s [%s]", + glade_widget_adaptor_get_display_name (adaptor), + glade_widget_get_name (gwidget)); + + _glade_dnd_set_icon_widget (context, + glade_widget_adaptor_get_icon_name (adaptor), + description); + + g_free (description); +} + +static void +glade_design_layout_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *data, + guint info, + guint time) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + _glade_dnd_set_data (data, G_OBJECT (priv->drag_source)); +} + +static void +glade_design_layout_drag_end (GtkWidget *widget, GdkDragContext *context) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private ((GladeDesignLayout *) widget); + priv->drag_source = NULL; +} + +static void +glade_design_layout_class_init (GladeDesignLayoutClass * klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkContainerClass *container_class; + GtkCssProvider *css_provider; + + object_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + container_class = GTK_CONTAINER_CLASS (klass); + + object_class->constructor = glade_design_layout_constructor; + object_class->dispose = glade_design_layout_dispose; + object_class->finalize = glade_design_layout_finalize; + object_class->set_property = glade_design_layout_set_property; + object_class->get_property = glade_design_layout_get_property; + + container_class->add = glade_design_layout_add; + container_class->remove = glade_design_layout_remove; + + widget_class->realize = glade_design_layout_realize; + widget_class->unrealize = glade_design_layout_unrealize; + widget_class->motion_notify_event = glade_design_layout_motion_notify_event; + widget_class->enter_notify_event = glade_design_layout_enter_leave_notify_event; + widget_class->leave_notify_event = glade_design_layout_enter_leave_notify_event; + widget_class->button_press_event = glade_design_layout_button_press_event; + widget_class->button_release_event = glade_design_layout_button_release_event; + widget_class->draw = glade_design_layout_draw; + widget_class->get_preferred_height = glade_design_layout_get_preferred_height; + widget_class->get_preferred_width = glade_design_layout_get_preferred_width; + widget_class->get_preferred_width_for_height = glade_design_layout_get_preferred_width_for_height; + widget_class->get_preferred_height_for_width = glade_design_layout_get_preferred_height_for_width; + widget_class->size_allocate = glade_design_layout_size_allocate; + widget_class->style_updated = glade_design_layout_style_updated; + widget_class->drag_begin = glade_design_layout_drag_begin; + widget_class->drag_end = glade_design_layout_drag_end; + widget_class->drag_data_get = glade_design_layout_drag_data_get; + + g_object_class_install_property (object_class, PROP_DESIGN_VIEW, + g_param_spec_object ("design-view", _("Design View"), + _("The GladeDesignView that contains this layout"), + GLADE_TYPE_DESIGN_VIEW, + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_signal_override_class_closure (g_signal_lookup ("damage-event", GTK_TYPE_WIDGET), + GLADE_TYPE_DESIGN_LAYOUT, + g_cclosure_new (G_CALLBACK (glade_design_layout_damage), + NULL, NULL)); + + /* Setup Custom CSS */ + gtk_widget_class_set_css_name (widget_class, "glade-design-layout"); + + css_provider = gtk_css_provider_new (); + gtk_css_provider_load_from_resource (css_provider, "/org/gnome/gladeui/glade-design-layout.css"); + + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (css_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + g_object_unref (css_provider); +} + +/* Internal API */ + +GtkWidget * +_glade_design_layout_new (GladeDesignView *view) +{ + return g_object_new (GLADE_TYPE_DESIGN_LAYOUT, "design-view", view, NULL); +} + +void +_glade_design_layout_draw_node (cairo_t *cr, + gdouble x, + gdouble y, + GdkRGBA *fg, + GdkRGBA *bg) +{ + cairo_new_sub_path (cr); + cairo_arc (cr, x, y, OUTLINE_WIDTH, 0, 2*G_PI); + + gdk_cairo_set_source_rgba (cr, bg); + cairo_stroke_preserve (cr); + + gdk_cairo_set_source_rgba (cr, fg); + cairo_fill (cr); +} + +void +_glade_design_layout_draw_pushpin (cairo_t *cr, + gdouble needle_length, + GdkRGBA *outline, + GdkRGBA *fill, + GdkRGBA *bg, + GdkRGBA *fg) +{ + cairo_save (cr); + + /* Draw needle */ + cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT); + cairo_set_line_width (cr, 1); + + cairo_move_to (cr, 1, 2); + cairo_line_to (cr, 1, 2+needle_length); + cairo_set_source_rgba (cr, bg->red, bg->green, bg->blue, .9); + cairo_stroke(cr); + + cairo_move_to (cr, 0, 2); + cairo_line_to (cr, 0, 2+needle_length); + gdk_cairo_set_source_rgba (cr, fg); + cairo_stroke (cr); + + /* Draw top and bottom fat lines */ + cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND); + + cairo_move_to (cr, -4, 0); + cairo_line_to (cr, 4, 0); + + cairo_move_to (cr, -2.5, -7); + cairo_line_to (cr, 2.5, -7); + + gdk_cairo_set_source_rgba (cr, outline); + cairo_set_line_width (cr, 4); + cairo_stroke_preserve (cr); + + gdk_cairo_set_source_rgba (cr, fill); + cairo_set_line_width (cr, 2); + cairo_stroke (cr); + + /* Draw middle section */ + cairo_move_to (cr, -2, -5); + cairo_line_to (cr, 2, -5); + cairo_line_to (cr, 3, -2); + cairo_line_to (cr, -3, -2); + cairo_close_path (cr); + + gdk_cairo_set_source_rgba (cr, outline); + cairo_set_line_width (cr, 2); + cairo_stroke_preserve (cr); + gdk_cairo_set_source_rgba (cr, fill); + cairo_fill (cr); + + /* Draw middle section shadow */ + cairo_set_source_rgb (cr, fill->red-.16, fill->green-.16, fill->blue-.16); + cairo_set_line_width (cr, 1); + cairo_move_to (cr, 1, -5); + cairo_line_to (cr, 1.5, -2); + cairo_stroke (cr); + + cairo_restore (cr); +} + +static inline void +_glade_design_layout_coords_from_event (GdkWindow *parent, + GdkEvent *event, + gint *x, gint *y) +{ + GdkWindow *child = event->any.window; + gdouble xx, yy; + + if (!gdk_event_get_coords (event, &xx, &yy)) + { + *x = *y = 0; + g_warning ("wrong event type %d", event->type); + return; + } + + while (child && parent != child) + { + gdk_window_coords_to_parent (child, xx, yy, &xx, &yy); + child = gdk_window_get_parent (child); + } + + *x = xx; + *y = yy; +} + +void +_glade_design_layout_get_colors (GdkRGBA *c1, GdkRGBA *c2, + GdkRGBA *c3, GdkRGBA *c4) +{ + GtkStyleContext *context = gtk_style_context_new (); + GtkWidgetPath *path = gtk_widget_path_new (); + gfloat off; + + /* Fake style context */ + gtk_widget_path_append_type (path, GTK_TYPE_WIDGET); + gtk_style_context_set_path (context, path); + gtk_style_context_add_class (context, GTK_STYLE_CLASS_VIEW); + + /* Get colors */ + gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL); + gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), c1); + gtk_style_context_get_color (context, gtk_style_context_get_state (context), c2); + + gtk_style_context_set_state (context, GTK_STATE_FLAG_NORMAL | GTK_STATE_FLAG_SELECTED | GTK_STATE_FLAG_FOCUSED); + gtk_style_context_set_state (context, gtk_style_context_get_state (context)); + gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), c3); + gtk_style_context_get_color (context, gtk_style_context_get_state (context), c4); + + off = ((c1->red + c1->green + c1->blue)/3 < .5) ? .16 : -.16; + + c1->red += off; + c1->green += off; + c1->blue += off; + + /* Free resources */ + gtk_widget_path_free (path); + g_object_unref (context); +} + +void +_glade_design_layout_get_hot_point (GladeDesignLayout *layout, + gint *x, + gint *y) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + if (x) + *x = priv->drag_x; + + if (y) + *y = priv->drag_y; +} + +void +_glade_design_layout_set_highlight (GladeDesignLayout *layout, + GladeWidget *drag) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + g_clear_object (&priv->drag_dest); + + if (drag) + priv->drag_dest = g_object_ref (drag); + + gtk_widget_queue_draw (GTK_WIDGET (layout)); +} + +typedef struct +{ + GtkWidget *toplevel; + gint x, y; + GtkWidget *child; + gint level; +} FindInContainerData; + +static void +find_first_child_inside_container (GtkWidget *widget, FindInContainerData *data) +{ + gint x, y, w, h; + + if (data->child || !gtk_widget_get_mapped (widget)) + return; + + gtk_widget_translate_coordinates (data->toplevel, widget, data->x, data->y, + &x, &y); + + /* Margins are not part of the widget allocation */ + w = gtk_widget_get_allocated_width (widget) + get_margin_right (widget); + h = gtk_widget_get_allocated_height (widget) + get_margin_bottom (widget); + + if (x >= (0 - get_margin_left (widget)) && x < w && + y >= (0 - get_margin_top (widget)) && y < h) + { + if (GLADE_IS_PLACEHOLDER (widget)) + data->child = widget; + else + { + GladeWidget *gwidget = glade_widget_get_from_gobject (widget); + + if (GTK_IS_CONTAINER (widget)) + { + if (gwidget) + data->child = _glade_design_layout_get_child_at_position (widget, x, y); + else + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) find_first_child_inside_container, + data); + } + + if (!data->child && gwidget) + data->child = widget; + } + } +} + +static void +find_last_child_inside_container (GtkWidget *widget, FindInContainerData *data) +{ + gint x, y, w, h; + + if ((data->child && data->level) || !gtk_widget_get_mapped (widget)) + return; + + gtk_widget_translate_coordinates (data->toplevel, widget, data->x, data->y, + &x, &y); + + /* Margins are not part of the widget allocation */ + w = gtk_widget_get_allocated_width (widget) + get_margin_right (widget); + h = gtk_widget_get_allocated_height (widget) + get_margin_bottom (widget); + + if (x >= (0 - get_margin_left (widget)) && x < w && + y >= (0 - get_margin_top (widget)) && y < h) + { + GladeWidget *gwidget = glade_widget_get_from_gobject (widget); + + if (GTK_IS_CONTAINER (widget)) + { + if (!data->level) + data->child = NULL; + + data->level++; + + if (gwidget) + data->child = _glade_design_layout_get_child_at_position (widget, x, y); + else + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback) find_last_child_inside_container, + data); + data->level--; + } + + if (data->level) + { + if (!data->child && (GLADE_IS_PLACEHOLDER (widget) || gwidget)) + data->child = widget; + } + else if ((!data->child || data->toplevel == gtk_widget_get_parent (data->child)) && + (GLADE_IS_PLACEHOLDER (widget) || gwidget)) + data->child = widget; + } +} + +GtkWidget * +_glade_design_layout_get_child_at_position (GtkWidget *widget, gint x, gint y) +{ + gboolean find_last; + + if (!gtk_widget_get_mapped (widget)) + return NULL; + + find_last = (GTK_IS_FIXED (widget) || GTK_IS_LAYOUT (widget) || GTK_IS_OVERLAY (widget)); + + if (x >= 0 && x <= gtk_widget_get_allocated_width (widget) && + y >= 0 && y <= gtk_widget_get_allocated_height (widget)) + { + if (GTK_IS_CONTAINER (widget)) + { + FindInContainerData data = {widget, x, y, NULL, 0}; + + if (find_last) + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback)find_last_child_inside_container, + &data); + else + gtk_container_forall (GTK_CONTAINER (widget), + (GtkCallback)find_first_child_inside_container, + &data); + + return (data.child) ? data.child : widget; + } + else + return widget; + } + + return NULL; +} + +static inline gboolean +gdl_get_child_from_event (GladeDesignLayout *layout, + GdkEvent *event, + GladeWidget **gwidget, + GtkWidget **placeholder, + gint *x, + gint *y) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GtkWidget *child; + + if (!priv->gchild) + return TRUE; + + _glade_design_layout_coords_from_event (priv->window, event, x, y); + + child = GTK_WIDGET (glade_widget_get_object (priv->gchild)); + if ((child = _glade_design_layout_get_child_at_position (child, + *x - priv->child_offset, + *y - priv->child_offset))) + { + if (GLADE_IS_PLACEHOLDER (child)) + { + *gwidget = glade_placeholder_get_parent (GLADE_PLACEHOLDER (child)); + *placeholder = child; + } + else + { + *gwidget = glade_widget_get_from_gobject (child); + *placeholder = NULL; + } + + return FALSE; + } + + return TRUE; +} + +static inline void +gdl_drag_source_check (GladeDesignLayout *layout, + GladePointerMode mode, + GdkEvent *event, + GladeWidget *gwidget, + gint x, + gint y) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + if (mode == GLADE_POINTER_SELECT && + event->type == GDK_BUTTON_PRESS && + event->button.button == 1) + { + GObject *source; + + if (gwidget && (source = glade_widget_get_object (gwidget)) && + !(event->button.state & GDK_SHIFT_MASK) && + _glade_drag_can_drag (GLADE_DRAG (gwidget))) + { + priv->drag_source = GTK_WIDGET (source); + + gtk_widget_translate_coordinates (GTK_WIDGET (layout), + priv->drag_source, x, y, + &priv->drag_x, &priv->drag_y); + } + else + { + priv->drag_source = NULL; + } + } + else if (event->type == GDK_BUTTON_RELEASE && event->button.button == 1) + { + priv->drag_source = NULL; + } +} + +GladeWidget * +_glade_design_layout_get_child (GladeDesignLayout *layout) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + + return priv->gchild; +} + +/* + * _glade_design_layout_do_event: + * @layout: A #GladeDesignLayout + * @event: an event to process + * + * Process events to make widget selection work. This function should be called + * before the child widget get the event. See gdk_event_handler_set() + * + * Returns: true if the event was handled. + */ +gboolean +_glade_design_layout_do_event (GladeDesignLayout *layout, GdkEvent *event) +{ + GladeDesignLayoutPrivate *priv = glade_design_layout_get_instance_private (layout); + GtkWidget *widget = GTK_WIDGET (layout); + GtkWidget *placeholder; + GladeWidget *gwidget; + GladePointerMode mode; + gboolean retval; + gint x, y; + GList *l; + + if (gdl_get_child_from_event (layout, event, &gwidget, &placeholder, &x, &y)) + return FALSE; + + mode = glade_project_get_pointer_mode (priv->project); + + /* Check if we want to enter in margin edit mode */ + if ((event->type == GDK_BUTTON_PRESS || event->type == GDK_2BUTTON_PRESS) && + !(event->button.state & GDK_SHIFT_MASK) && /* SHIFT is reserved for Drag&Resize */ + mode != GLADE_POINTER_DRAG_RESIZE && /* This mode is for adaptors implementations */ + (l = glade_project_selection_get (priv->project)) && + g_list_next (l) == NULL && GTK_IS_WIDGET (l->data) && + gtk_widget_is_ancestor (l->data, widget)) + { + if (gdl_get_margins_from_pointer (layout, l->data, x, y)) + { + if (event->button.button == 2) + { + glade_project_set_pointer_mode (priv->project, + (mode == GLADE_POINTER_MARGIN_EDIT) ? + GLADE_POINTER_ALIGN_EDIT : + GLADE_POINTER_MARGIN_EDIT); + return TRUE; + } + else if (event->button.button == 1 && priv->selection == NULL) + { + gdl_edit_mode_set_selection (layout, GLADE_POINTER_MARGIN_EDIT, + l->data); + return TRUE; + } + + return FALSE; + } + } + + /* Check if this event could start a drag event and save the initial + * coordinates for later. + */ + gdl_drag_source_check (layout, mode, event, gwidget, x, y); + + /* Try the placeholder first */ + if (placeholder && gtk_widget_event (placeholder, event)) + retval = TRUE; + else if (gwidget) /* Then we try a GladeWidget */ + retval = glade_widget_event (gwidget, event); + else + retval = FALSE; + + return retval; +} diff --git a/gladeui/glade-design-layout.css b/gladeui/glade-design-layout.css new file mode 100644 index 0000000..c609ac1 --- /dev/null +++ b/gladeui/glade-design-layout.css @@ -0,0 +1,55 @@ +/* + * glade-design-layout.css + * + * Copyright (C) 2016 Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Author: Juan Pablo Ugarte + * + */ + +glade-design-layout { + background: none; + border: 4px solid @borders; + color: @theme_selected_fg_color; + border-radius: 4px 4px 0px 4px; +} + +glade-design-layout:selected { + border: 4px solid @theme_selected_bg_color; +} + +glade-design-layout.handle { + background-color: @borders; + border-radius: 0px 0px 4px 4px; +} + +glade-design-layout:selected.handle { + background-color: @theme_selected_bg_color; + border-radius: 0px 0px 4px 4px; +} + +glade-design-layout.selection { + border: 2px solid @theme_selected_bg_color; + border-radius: 0px; + background-color:transparent; + background-image: -gtk-gradient (radial, + center center, 0, + center center, 1, + from (alpha(@theme_selected_bg_color, .08)), + to (alpha(@theme_selected_bg_color, .50))); +} + diff --git a/gladeui/glade-design-layout.h b/gladeui/glade-design-layout.h new file mode 100644 index 0000000..39fdf25 --- /dev/null +++ b/gladeui/glade-design-layout.h @@ -0,0 +1,50 @@ +/* + * glade-design-layout.h + * + * Copyright (C) 2006-2007 Vincent Geddes + * + * Authors: + * Vincent Geddes + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GLADE_DESIGN_LAYOUT_H__ +#define __GLADE_DESIGN_LAYOUT_H__ + +#include +#include "glade-design-view.h" + +G_BEGIN_DECLS + +#define GLADE_TYPE_DESIGN_LAYOUT glade_design_layout_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeDesignLayout, glade_design_layout, GLADE, DESIGN_LAYOUT, GtkBin) + +struct _GladeDesignLayoutClass +{ + GtkBinClass parent_class; + + gpointer padding[5]; +}; + +GtkWidget *_glade_design_layout_new (GladeDesignView *view); + +gboolean _glade_design_layout_do_event (GladeDesignLayout *layout, + GdkEvent *event); + +G_END_DECLS + +#endif /* __GLADE_DESIGN_LAYOUT_H__ */ diff --git a/gladeui/glade-design-private.h b/gladeui/glade-design-private.h new file mode 100644 index 0000000..47a7148 --- /dev/null +++ b/gladeui/glade-design-private.h @@ -0,0 +1,65 @@ +/* + * glade-design-private.h + * + * Copyright (C) 2011 Juan Pablo Ugarte + * + * Authors: + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GLADE_DESIGN_PRIVATE_H__ +#define __GLADE_DESIGN_PRIVATE_H__ + +#include "glade-design-view.h" +#include "glade-design-layout.h" +#include "glade-dnd.h" + +G_BEGIN_DECLS + +void _glade_design_layout_get_colors (GdkRGBA *c1, GdkRGBA *c2, + GdkRGBA *c3, GdkRGBA *c4); + +void _glade_design_layout_draw_node (cairo_t *cr, + gdouble x, + gdouble y, + GdkRGBA *fg, + GdkRGBA *bg); + +void _glade_design_layout_draw_pushpin (cairo_t *cr, + gdouble needle_length, + GdkRGBA *outline, + GdkRGBA *fill, + GdkRGBA *bg, + GdkRGBA *fg); + +void _glade_design_layout_get_hot_point (GladeDesignLayout *layout, + gint *x, + gint *y); + +GladeWidget * _glade_design_layout_get_child (GladeDesignLayout *layout); + +GtkWidget *_glade_design_layout_get_child_at_position (GtkWidget *widget, + gint x, + gint y); + +void _glade_design_layout_set_highlight (GladeDesignLayout *layout, + GladeWidget *drag); + +G_END_DECLS + +#endif /* __GLADE_DESIGN_PRIVATE_H__ */ diff --git a/gladeui/glade-design-view.c b/gladeui/glade-design-view.c new file mode 100644 index 0000000..82887ac --- /dev/null +++ b/gladeui/glade-design-view.c @@ -0,0 +1,866 @@ +/* + * glade-design-view.c + * + * Copyright (C) 2006 Vincent Geddes + * 2011-2016 Juan Pablo Ugarte + * + * Authors: + * Vincent Geddes + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +/** + * SECTION:glade-design-view + * @Title: GladeDesignView + * @Short_Description: A widget to embed the workspace. + * + * Use this widget to embed toplevel widgets in a given #GladeProject. + */ + +#include "config.h" + +#include "glade.h" +#include "glade-dnd.h" +#include "glade-utils.h" +#include "glade-design-view.h" +#include "glade-design-layout.h" +#include "glade-design-private.h" +#include "glade-path.h" +#include "glade-adaptor-chooser-widget.h" + +#include +#include + +#define GLADE_DESIGN_VIEW_KEY "GLADE_DESIGN_VIEW_KEY" + +enum +{ + PROP_0, + PROP_PROJECT +}; + +typedef struct _GladeDesignViewPrivate +{ + GladeProject *project; + GtkWidget *scrolled_window; /* Main scrolled window */ + GtkWidget *layout_box; /* Box to pack a GladeDesignLayout for each toplevel in project */ + + _GladeDrag *drag_target; + GObject *drag_data; + gboolean drag_highlight; +} GladeDesignViewPrivate; + +static GtkVBoxClass *parent_class = NULL; + +static void glade_design_view_drag_init (_GladeDragInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GladeDesignView, glade_design_view, GTK_TYPE_BOX, + G_ADD_PRIVATE (GladeDesignView) + G_IMPLEMENT_INTERFACE (GLADE_TYPE_DRAG, + glade_design_view_drag_init)) + +static void +glade_design_layout_scroll (GladeDesignView *view, gint x, gint y, gint w, gint h) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + gdouble vadj_val, hadj_val, vpage_end, hpage_end; + GtkAdjustment *vadj, *hadj; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + vadj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (priv->scrolled_window)); + hadj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (priv->scrolled_window)); + + vadj_val = gtk_adjustment_get_value (vadj); + hadj_val = gtk_adjustment_get_value (hadj); + vpage_end = gtk_adjustment_get_page_size (vadj) + vadj_val; + hpage_end = gtk_adjustment_get_page_size (hadj) + hadj_val; + + /* TODO: we could set this value in increments in a timeout callback + * to make it look like its scrolling instead of jumping. + */ + if (y < vadj_val || y > vpage_end || (y + h) > vpage_end) + gtk_adjustment_set_value (vadj, y); + + if (x < hadj_val || x > hpage_end || (x + w) > hpage_end) + gtk_adjustment_set_value (hadj, x); +} + +static void +on_layout_size_allocate (GtkWidget *widget, GtkAllocation *alloc, GladeDesignView *view) +{ + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + glade_design_layout_scroll (view, alloc->x, alloc->y, alloc->width, alloc->height); + g_signal_handlers_disconnect_by_func (widget, on_layout_size_allocate, view); +} + +static void +glade_design_view_update_state (GList *objects, GtkStateFlags state) +{ + GList *l; + + for (l = objects; l && l->data; l = g_list_next (l)) + { + GtkWidget *view, *widget = l->data; + + if (GTK_IS_WIDGET (widget) && + gtk_widget_get_visible (widget) && + (view = gtk_widget_get_ancestor (widget, GLADE_TYPE_DESIGN_LAYOUT))) + { + gtk_widget_set_state_flags (view, state, TRUE); + } + } +} + +static void +glade_design_view_selection_changed (GladeProject *project, GladeDesignView *view) +{ + GtkWidget *layout; + GList *selection; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + glade_design_view_update_state (glade_project_toplevels (project), + GTK_STATE_FLAG_NORMAL); + + if (!(selection = glade_project_selection_get (project))) + return; + + glade_design_view_update_state (selection, GTK_STATE_FLAG_SELECTED); + + /* Check if its only one widget selected and scroll viewport to show toplevel */ + if (g_list_next (selection) == NULL && + GTK_IS_WIDGET (selection->data) && + (layout = gtk_widget_get_ancestor (selection->data, GLADE_TYPE_DESIGN_LAYOUT))) + { + GtkAllocation alloc; + gtk_widget_get_allocation (layout, &alloc); + + if (alloc.x < 0) + g_signal_connect (layout, "size-allocate", G_CALLBACK (on_layout_size_allocate), view); + else + glade_design_layout_scroll (view, alloc.x, alloc.y, alloc.width, alloc.height); + } +} + +static void +glade_design_view_add_toplevel (GladeDesignView *view, GladeWidget *widget) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + GtkWidget *layout; + GList *toplevels; + GObject *object; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (glade_widget_get_parent (widget) || + (object = glade_widget_get_object (widget)) == NULL || + !GTK_IS_WIDGET (object) || + gtk_widget_get_parent (GTK_WIDGET (object))) + return; + + /* Create a GladeDesignLayout and add the toplevel widget to the view */ + layout = _glade_design_layout_new (view); + gtk_widget_set_halign (layout, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (priv->layout_box), layout, FALSE, FALSE, 0); + + if ((toplevels = glade_project_toplevels (priv->project))) + gtk_box_reorder_child (GTK_BOX (priv->layout_box), layout, + g_list_index (toplevels, GTK_WIDGET (object))); + + gtk_container_add (GTK_CONTAINER (layout), GTK_WIDGET (object)); + + gtk_widget_show (GTK_WIDGET (object)); + gtk_widget_show (layout); +} + +static void +glade_design_view_remove_toplevel (GladeDesignView *view, GladeWidget *widget) +{ + GtkWidget *layout; + GObject *object; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (glade_widget_get_parent (widget) || + (object = glade_widget_get_object (widget)) == NULL || + !GTK_IS_WIDGET (object)) return; + + /* Remove toplevel widget from the view */ + if ((layout = gtk_widget_get_parent (GTK_WIDGET (object))) && + gtk_widget_is_ancestor (layout, GTK_WIDGET (view))) + { + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + gtk_container_remove (GTK_CONTAINER (layout), GTK_WIDGET (object)); + gtk_container_remove (GTK_CONTAINER (priv->layout_box), layout); + } +} + +static void +glade_design_view_widget_visibility_changed (GladeProject *project, + GladeWidget *widget, + gboolean visible, + GladeDesignView *view) +{ + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (visible) + glade_design_view_add_toplevel (view, widget); + else + glade_design_view_remove_toplevel (view, widget); +} + +static void +on_project_add_widget (GladeProject *project, GladeWidget *widget, GladeDesignView *view) +{ + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + glade_design_view_add_toplevel (view, widget); +} + +static void +on_project_remove_widget (GladeProject *project, GladeWidget *widget, GladeDesignView *view) +{ + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + glade_design_view_remove_toplevel (view, widget); +} + +static void +glade_design_view_set_project (GladeDesignView *view, GladeProject *project) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (priv->project) + { + g_object_remove_weak_pointer (G_OBJECT (priv->project), (gpointer *) &priv->project); + + g_signal_handlers_disconnect_by_data (priv->project, view); + g_signal_handlers_disconnect_by_data (priv->project, priv->scrolled_window); + + g_object_set_data (G_OBJECT (priv->project), GLADE_DESIGN_VIEW_KEY, NULL); + } + + priv->project = project; + + if (!project) + return; + + g_assert (GLADE_IS_PROJECT (project)); + + g_object_add_weak_pointer (G_OBJECT (project), (gpointer *) &priv->project); + + g_signal_connect (project, "add-widget", + G_CALLBACK (on_project_add_widget), view); + g_signal_connect (project, "remove-widget", + G_CALLBACK (on_project_remove_widget), view); + g_signal_connect_swapped (project, "parse-began", + G_CALLBACK (gtk_widget_hide), + priv->scrolled_window); + g_signal_connect_swapped (project, "parse-finished", + G_CALLBACK (gtk_widget_show), + priv->scrolled_window); + g_signal_connect (project, "selection-changed", + G_CALLBACK (glade_design_view_selection_changed), view); + g_signal_connect (project, "widget-visibility-changed", + G_CALLBACK (glade_design_view_widget_visibility_changed), view); + + g_object_set_data (G_OBJECT (project), GLADE_DESIGN_VIEW_KEY, view); +} + +static void +glade_design_view_set_property (GObject *object, + guint prop_id, + const GValue *value, GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_PROJECT: + glade_design_view_set_project (GLADE_DESIGN_VIEW (object), + g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_design_view_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private ((GladeDesignView *) object); + + switch (prop_id) + { + case PROP_PROJECT: + g_value_set_object (value, priv->project); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +logo_draw (GtkWidget *widget, cairo_t *cr, GdkRGBA *c) +{ + GtkAllocation alloc; + gdouble scale; + + gtk_widget_get_allocation (widget, &alloc); + + cairo_save (cr); + + cairo_set_source_rgba (cr, c->red, c->green, c->blue, .06); + + scale = MIN ((alloc.width/1.5)/(glade_path_WIDTH), (alloc.height/1.5)/(glade_path_HEIGHT)); + + cairo_scale (cr, scale, scale); + + cairo_translate (cr, (alloc.width / scale) - glade_path_WIDTH, + (alloc.height / scale) - glade_path_HEIGHT); + cairo_append_path (cr, &glade_path); + cairo_fill (cr); + + cairo_restore (cr); +} + +static void +on_chooser_adaptor_widget_selected (_GladeAdaptorChooserWidget *chooser, + GladeWidgetAdaptor *adaptor, + GladeProject *project) + +{ + glade_command_create (adaptor, NULL, NULL, project); + gtk_widget_destroy (gtk_widget_get_ancestor (GTK_WIDGET (chooser), GTK_TYPE_POPOVER)); +} + +static gboolean +glade_design_view_viewport_button_press (GtkWidget *widget, + GdkEventButton *event, + GladeDesignView *view) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private ((GladeDesignView *) view); + GdkRectangle rect = {event->x, event->y, 8, 8}; + GtkWidget *pop, *chooser; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (event->type != GDK_2BUTTON_PRESS) + return FALSE; + + pop = gtk_popover_new (widget); + gtk_popover_set_pointing_to (GTK_POPOVER (pop), &rect); + gtk_popover_set_position (GTK_POPOVER (pop), GTK_POS_BOTTOM); + + chooser = _glade_adaptor_chooser_widget_new (GLADE_ADAPTOR_CHOOSER_WIDGET_TOPLEVEL | + GLADE_ADAPTOR_CHOOSER_WIDGET_WIDGET | + GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_DEPRECATED, + priv->project); + _glade_adaptor_chooser_widget_populate (GLADE_ADAPTOR_CHOOSER_WIDGET (chooser)); + g_signal_connect (chooser, "adaptor-selected", + G_CALLBACK (on_chooser_adaptor_widget_selected), + priv->project); + + gtk_container_add (GTK_CONTAINER (pop), chooser); + gtk_widget_show (chooser); + gtk_popover_popup (GTK_POPOVER (pop)); + + return TRUE; +} + +static gboolean +glade_design_view_viewport_draw (GtkWidget *widget, cairo_t *cr, GladeDesignView *view) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private ((GladeDesignView *) view); + GtkStyleContext *context = gtk_widget_get_style_context (widget); + GdkRGBA fg_color; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + gtk_style_context_get_color (context, gtk_style_context_get_state (context), + &fg_color); + + logo_draw (widget, cr, &fg_color); + + if (priv->drag_highlight) + { + GdkRGBA c; + + gtk_style_context_save (context); + gtk_style_context_get_background_color (context, + gtk_style_context_get_state (context) | + GTK_STATE_FLAG_SELECTED | + GTK_STATE_FLAG_FOCUSED, &c); + gtk_style_context_restore (context); + + cairo_set_line_width (cr, 2); + gdk_cairo_set_source_rgba (cr, &c); + cairo_rectangle (cr, 0, 0, + gtk_widget_get_allocated_width (widget), + gtk_widget_get_allocated_height (widget)); + cairo_stroke (cr); + } + + return FALSE; +} + + +static void +glade_design_view_init (GladeDesignView *view) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + GtkWidget *viewport; + + gtk_widget_set_no_show_all (GTK_WIDGET (view), TRUE); + gtk_orientable_set_orientation (GTK_ORIENTABLE (view), + GTK_ORIENTATION_VERTICAL); + + priv->project = NULL; + priv->layout_box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); + gtk_widget_set_valign (priv->layout_box, GTK_ALIGN_START); + gtk_container_set_border_width (GTK_CONTAINER (priv->layout_box), 0); + + priv->scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (priv->scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + + viewport = gtk_viewport_new (NULL, NULL); + gtk_widget_add_events (viewport, GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK); + g_signal_connect (viewport, "button-press-event", + G_CALLBACK (glade_design_view_viewport_button_press), + view); + g_signal_connect (viewport, "draw", + G_CALLBACK (glade_design_view_viewport_draw), + view); + gtk_viewport_set_shadow_type (GTK_VIEWPORT (viewport), GTK_SHADOW_NONE); + gtk_container_add (GTK_CONTAINER (viewport), priv->layout_box); + gtk_container_add (GTK_CONTAINER (priv->scrolled_window), viewport); + + gtk_widget_show (priv->scrolled_window); + gtk_widget_show (viewport); + gtk_widget_show_all (priv->layout_box); + + gtk_box_pack_start (GTK_BOX (view), priv->scrolled_window, TRUE, TRUE, 0); + + gtk_container_set_border_width (GTK_CONTAINER (view), 0); + + gtk_style_context_add_class (gtk_widget_get_style_context (GTK_WIDGET (view)), + GTK_STYLE_CLASS_VIEW); + + _glade_dnd_dest_set (GTK_WIDGET (view)); +} + +static void +glade_design_view_dispose (GObject *object) +{ + GladeDesignView *view = GLADE_DESIGN_VIEW (object); + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + glade_design_view_set_project (view, NULL); + g_clear_object (&priv->drag_target); + g_clear_object (&priv->drag_data); + + G_OBJECT_CLASS (parent_class)->dispose (object); +} + +typedef struct +{ + GtkWidget *child; + gint x, y; +} FindInContainerData; + +static void +find_inside_container (GtkWidget *widget, FindInContainerData *data) +{ + GtkAllocation alloc; + gint x, y; + + if (data->child || !gtk_widget_get_mapped (widget)) + return; + + x = data->x; + y = data->y; + gtk_widget_get_allocation (widget, &alloc); + + if (x >= alloc.x && x <= (alloc.x + alloc.width) && + y >= alloc.y && y <= (alloc.y + alloc.height)) + { + data->child = widget; + } +} + +static void +glade_design_view_drag_highlight (_GladeDrag *dest, + gint x, + gint y) +{ + if (GLADE_IS_WIDGET (dest)) + { + GObject *obj = glade_widget_get_object (GLADE_WIDGET (dest)); + + if (GTK_IS_WIDGET (obj)) + { + GtkWidget *layout = gtk_widget_get_ancestor (GTK_WIDGET (obj), + GLADE_TYPE_DESIGN_LAYOUT); + if (layout) + _glade_design_layout_set_highlight (GLADE_DESIGN_LAYOUT (layout), + (x<0 || y<0) ? NULL : GLADE_WIDGET (dest)); + } + } + + _glade_drag_highlight (dest, x, y); +} + +static gboolean +glade_design_view_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, gint y, + guint time) +{ + GladeDesignView *view = GLADE_DESIGN_VIEW (widget); + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + FindInContainerData data; + _GladeDrag *drag = NULL; + gint xx, yy; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (!priv->drag_data) + { + GdkAtom target = gtk_drag_dest_find_target (widget, context, NULL); + + if (target) + gtk_drag_get_data (widget, context, target, time); + } + + data.child = NULL; + gtk_widget_translate_coordinates (widget, GTK_WIDGET (priv->layout_box), + x, y, &data.x, &data.y); + gtk_container_forall (GTK_CONTAINER (priv->layout_box), + (GtkCallback) find_inside_container, + &data); + + if (data.child) + { + GladeDesignLayout *layout = GLADE_DESIGN_LAYOUT (data.child); + GladeWidget *gchild = _glade_design_layout_get_child (layout); + GtkWidget *child = GTK_WIDGET (glade_widget_get_object (gchild)); + GtkWidget *drag_target; + + gtk_widget_translate_coordinates (widget, child, x, y, &xx, &yy); + + drag_target = _glade_design_layout_get_child_at_position (child, xx, yy); + + if (drag_target) + { + _GladeDrag *gwidget = NULL; + + if (GLADE_IS_PLACEHOLDER (drag_target)) { + gwidget = (_GladeDrag *) drag_target; + } else if (GLADE_IS_WIDGET (drag_target)) { + gwidget = (_GladeDrag *) glade_widget_get_from_gobject ((GladeWidget *) drag_target); + } + + while (gwidget && !_glade_drag_can_drop (gwidget, + xx, yy, priv->drag_data)) { + if (GLADE_IS_WIDGET (gwidget)) { + gwidget = (_GladeDrag *) glade_widget_get_parent ((GladeWidget *) gwidget); + } else if (GLADE_IS_PLACEHOLDER (gwidget)) { + gwidget = (_GladeDrag *) glade_placeholder_get_parent ((GladePlaceholder *) gwidget); + } else { + gwidget = NULL; + } + } + + if (gwidget) { + drag = GLADE_DRAG (gwidget); + } + } + } + else if (_glade_drag_can_drop (GLADE_DRAG (widget), x, y, priv->drag_data)) + { + drag = GLADE_DRAG (widget); + xx = x; + yy = y; + } + + if (priv->drag_target && priv->drag_target != drag) + { + glade_design_view_drag_highlight (priv->drag_target, -1, -1); + g_clear_object (&priv->drag_target); + } + + if (drag) + { + priv->drag_target = g_object_ref (drag); + + glade_design_view_drag_highlight (drag, xx, yy); + + gdk_drag_status (context, GDK_ACTION_COPY, time); + return TRUE; + } + + gdk_drag_status (context, 0, time); + return FALSE; +} + +static void +glade_design_view_drag_leave (GtkWidget *widget, + GdkDragContext *context, + guint time) +{ + GladeDesignView *view = GLADE_DESIGN_VIEW (widget); + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (priv->drag_target) + glade_design_view_drag_highlight (priv->drag_target, -1, -1); +} + +static void +on_source_drag_end (GtkWidget *widget, + GdkDragContext *context, + GladeDesignView *view) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (priv->drag_target) + { + glade_design_view_drag_highlight (priv->drag_target, -1, -1); + g_clear_object (&priv->drag_target); + } + + g_clear_object (&priv->drag_data); +} + +static void +glade_design_view_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection, + guint info, + guint time) +{ + GtkWidget *source = gtk_drag_get_source_widget (context); + GladeDesignView *view = GLADE_DESIGN_VIEW (widget); + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + g_signal_handlers_disconnect_by_func (source, on_source_drag_end, view); + + g_set_object (&priv->drag_data, _glade_dnd_get_data (context, selection, info)); + + g_signal_connect_object (source, "drag-end", G_CALLBACK (on_source_drag_end), view, 0); +} + +static gboolean +glade_design_view_drag_drop (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + GladeDesignView *view = GLADE_DESIGN_VIEW (widget); + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (priv->drag_data && priv->drag_target) + { + GtkWidget *target; + gint xx, yy; + + if (GLADE_IS_WIDGET (priv->drag_target)) + target = GTK_WIDGET (glade_widget_get_object (GLADE_WIDGET (priv->drag_target))); + else + target = GTK_WIDGET (priv->drag_target); + + gtk_widget_translate_coordinates (widget, target, x, y, &xx, &yy); + _glade_drag_drop (GLADE_DRAG (priv->drag_target), xx, yy, priv->drag_data); + gtk_drag_finish (context, TRUE, FALSE, time); + } + else + gtk_drag_finish (context, FALSE, FALSE, time); + + return TRUE; +} + +static gboolean +glade_design_view_drag_iface_can_drop (_GladeDrag *drag, + gint x, gint y, + GObject *data) +{ + GladeWidget *gwidget; + + if (GLADE_IS_WIDGET_ADAPTOR (data) || + ((gwidget = glade_widget_get_from_gobject (data)) && + glade_widget_get_parent (gwidget))) + return TRUE; + else + return FALSE; +} + +static gboolean +glade_design_view_drag_iface_drop (_GladeDrag *drag, + gint x, gint y, + GObject *data) +{ + GladeDesignView *view = GLADE_DESIGN_VIEW (drag); + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + GladeWidget *gsource; + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (GLADE_IS_WIDGET_ADAPTOR (data)) + { + glade_command_create (GLADE_WIDGET_ADAPTOR (data), + NULL, NULL, priv->project); + return TRUE; + } + else if ((gsource = glade_widget_get_from_gobject (data))) + { + GList widgets = {gsource, NULL, NULL}; + glade_command_dnd (&widgets, NULL, NULL); + return TRUE; + } + + return FALSE; +} + +static void +glade_design_view_drag_iface_highlight (_GladeDrag *drag, gint x, gint y) +{ + GladeDesignView *view = GLADE_DESIGN_VIEW (drag); + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + gboolean highlight = !(x < 0 || y < 0); + + g_assert (GLADE_IS_DESIGN_VIEW (view)); + + if (priv->drag_highlight == highlight) + return; + + priv->drag_highlight = highlight; + + gtk_widget_queue_draw (priv->scrolled_window); +} + +static void +glade_design_view_drag_init (_GladeDragInterface *iface) +{ + iface->can_drag = NULL; + iface->can_drop = glade_design_view_drag_iface_can_drop; + iface->drop = glade_design_view_drag_iface_drop; + iface->highlight = glade_design_view_drag_iface_highlight; +} + +static void +glade_design_view_class_init (GladeDesignViewClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + parent_class = g_type_class_peek_parent (klass); + object_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = glade_design_view_dispose; + object_class->get_property = glade_design_view_get_property; + object_class->set_property = glade_design_view_set_property; + + widget_class->drag_motion = glade_design_view_drag_motion; + widget_class->drag_leave = glade_design_view_drag_leave; + widget_class->drag_data_received = glade_design_view_drag_data_received; + widget_class->drag_drop = glade_design_view_drag_drop; + + g_object_class_install_property (object_class, + PROP_PROJECT, + g_param_spec_object ("project", + "Project", + "The project for this view", + GLADE_TYPE_PROJECT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY)); +} + +/* Public API */ + +/** + * glade_design_view_get_project: + * @view: A #GladeDesignView + * + * Returns: (transfer none): a #GladeProject + */ +GladeProject * +glade_design_view_get_project (GladeDesignView *view) +{ + GladeDesignViewPrivate *priv = glade_design_view_get_instance_private (view); + + g_return_val_if_fail (GLADE_IS_DESIGN_VIEW (view), NULL); + + return priv->project; + +} + +/** + * glade_design_view_new: + * @project: A #GladeProject + * + * Returns: (transfer full): a new #GladeDesignView + */ +GtkWidget * +glade_design_view_new (GladeProject *project) +{ + GladeDesignView *view; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + view = g_object_new (GLADE_TYPE_DESIGN_VIEW, "project", project, NULL); + + return GTK_WIDGET (view); +} + +/** + * glade_design_view_get_from_project: + * @project: A #GladeProject + * + * Returns: (transfer none) (nullable): a #GladeDesignView + */ +GladeDesignView * +glade_design_view_get_from_project (GladeProject *project) +{ + gpointer p; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + p = g_object_get_data (G_OBJECT (project), GLADE_DESIGN_VIEW_KEY); + + return (p != NULL) ? GLADE_DESIGN_VIEW (p) : NULL; +} diff --git a/gladeui/glade-design-view.h b/gladeui/glade-design-view.h new file mode 100644 index 0000000..ed20e41 --- /dev/null +++ b/gladeui/glade-design-view.h @@ -0,0 +1,53 @@ +/* + * glade-design-view.h + * + * Copyright (C) 2006 Vincent Geddes + * + * Authors: + * Vincent Geddes + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GLADE_DESIGN_VIEW_H__ +#define __GLADE_DESIGN_VIEW_H__ + +#include +#include + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_DESIGN_VIEW glade_design_view_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeDesignView, glade_design_view, GLADE, DESIGN_VIEW, GtkBox) + +struct _GladeDesignViewClass +{ + GtkBoxClass parent_class; + + gpointer padding[4]; +}; + +GtkWidget *glade_design_view_new (GladeProject *project); + +GladeProject *glade_design_view_get_project (GladeDesignView *view); + +GladeDesignView *glade_design_view_get_from_project (GladeProject *project); + +G_END_DECLS + +#endif /* __GLADE_DESIGN_VIEW_H__ */ diff --git a/gladeui/glade-displayable-values.c b/gladeui/glade-displayable-values.c new file mode 100644 index 0000000..da69b26 --- /dev/null +++ b/gladeui/glade-displayable-values.c @@ -0,0 +1,200 @@ +/* + * glade-name-context.c + * + * Copyright (C) 2008 Tristan Van Berkom. + * + * Authors: + * Tristan Van Berkom + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#include "glade-displayable-values.h" + + +typedef struct +{ + gchar *value; + gchar *string; + gboolean disabled:1; +} ValueTab; + +static GHashTable *values_hash = NULL; + + +static gint +find_by_value (ValueTab *a, const gchar *b) +{ + return strcmp (a->value, b); +} + + +static gint +find_by_displayable (ValueTab *a, const gchar *b) +{ + return strcmp (a->string, b); +} + +void +glade_register_displayable_value (GType type, + const gchar *value, + const gchar *domain, + const gchar *string) +{ + g_return_if_fail (value && value[0]); + g_return_if_fail (domain && domain[0]); + + glade_register_translated_value (type, value, dgettext (domain, string)); +} + +void +glade_register_translated_value (GType type, + const gchar *value, + const gchar *string) +{ + ValueTab *tab; + gpointer klass; + GList *values; + + g_return_if_fail (value && value[0]); + klass = g_type_class_ref (type); + g_return_if_fail (klass != NULL); + + if (!values_hash) + values_hash = g_hash_table_new (NULL, NULL); + + tab = g_new0 (ValueTab, 1); + tab->value = g_strdup (value); + tab->string = g_strdup (string); + tab->disabled = FALSE; + + if ((values = g_hash_table_lookup (values_hash, klass)) != NULL) + { + if (!g_list_find_custom + (values, tab->value, (GCompareFunc) find_by_value)) + values = g_list_append (values, tab); + else + { + g_warning ("Already registered displayable value %s for %s (type %s)", + tab->string, tab->value, g_type_name (type)); + g_free (tab->string); + g_free (tab->value); + g_free (tab); + } + } + else + { + values = g_list_append (NULL, tab); + g_hash_table_insert (values_hash, klass, values); + } + g_type_class_unref (klass); +} + +static ValueTab * +get_value_tab (GType type, const gchar *value, GCompareFunc cmpfunc) +{ + GList *values, *tab_iter; + gpointer klass; + ValueTab *tab; + + if (!values_hash) return NULL; + + klass = g_type_class_ref (type); + + if ((values = g_hash_table_lookup (values_hash, klass)) != NULL && + (tab_iter = g_list_find_custom (values, value, cmpfunc)) != NULL) + tab = tab_iter->data; + else + tab = NULL; + + g_type_class_unref (klass); + + return tab; +} + +gboolean +glade_type_has_displayable_values (GType type) +{ + gboolean has; + gpointer klass = g_type_class_ref (type); + + has = values_hash && g_hash_table_lookup (values_hash, klass) != NULL; + + g_type_class_unref (klass); + + return has; +} + +const gchar * +glade_get_displayable_value (GType type, const gchar *value) +{ + ValueTab *tab; + + g_return_val_if_fail (value && value[0], NULL); + + if ((tab = get_value_tab (type, value, (GCompareFunc) find_by_value))) + return tab->string; + + return NULL; +} + + +const gchar * +glade_get_value_from_displayable (GType type, const gchar *displayable) +{ + ValueTab *tab; + + g_return_val_if_fail (displayable && displayable[0], NULL); + + if ((tab = get_value_tab (type, displayable, (GCompareFunc) find_by_displayable))) + return tab->value; + + return NULL; +} + +gboolean +glade_displayable_value_is_disabled (GType type, const gchar *value) +{ + ValueTab *tab; + + g_return_val_if_fail (value && value[0], FALSE); + + if ((tab = get_value_tab (type, value, (GCompareFunc) find_by_value))) + return tab->disabled; + + return FALSE; +} + +void +glade_displayable_value_set_disabled (GType type, + const gchar *value, + gboolean disabled) +{ + ValueTab *tab; + + g_return_if_fail (value && value[0]); + + if ((tab = get_value_tab (type, value, (GCompareFunc) find_by_value))) + tab->disabled = disabled; +} diff --git a/gladeui/glade-displayable-values.h b/gladeui/glade-displayable-values.h new file mode 100644 index 0000000..602b1c6 --- /dev/null +++ b/gladeui/glade-displayable-values.h @@ -0,0 +1,37 @@ +#ifndef __GLADE_DISAPLAYABLE_VALUES_H__ +#define __GLADE_DISAPLAYABLE_VALUES_H__ + +#include +#include + +G_BEGIN_DECLS + +void glade_register_displayable_value (GType type, + const gchar *value, + const gchar *domain, + const gchar *string); + +void glade_register_translated_value (GType type, + const gchar *value, + const gchar *string); + +gboolean glade_type_has_displayable_values (GType type); + +const +gchar *glade_get_displayable_value (GType type, + const gchar *value); + +gboolean glade_displayable_value_is_disabled (GType type, + const gchar *value); + +void glade_displayable_value_set_disabled (GType type, + const gchar *value, + gboolean disabled); + +const +gchar *glade_get_value_from_displayable (GType type, + const gchar *displayabe); + +G_END_DECLS + +#endif /* __GLADE_DISAPLAYABLE_VALUES_H__ */ diff --git a/gladeui/glade-dnd.c b/gladeui/glade-dnd.c new file mode 100644 index 0000000..5217f75 --- /dev/null +++ b/gladeui/glade-dnd.c @@ -0,0 +1,175 @@ +/* + * glade-dnd.c + * + * Copyright (C) 2013 Juan Pablo Ugarte + * + * Authors: + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glade.h" +#include "glade-dnd.h" + +GtkTargetEntry * +_glade_dnd_get_target (void) +{ + static GtkTargetEntry target = {GLADE_DND_TARGET_DATA, GTK_TARGET_SAME_APP, GLADE_DND_INFO_DATA}; + return ⌖ +} + +void +_glade_dnd_dest_set (GtkWidget *target) +{ + gtk_drag_dest_set (target, 0, _glade_dnd_get_target (), 1, GDK_ACTION_MOVE | GDK_ACTION_COPY); +} + +GObject * +_glade_dnd_get_data (GdkDragContext *context, + GtkSelectionData *selection, + guint info) +{ + GdkAtom target = gtk_selection_data_get_target (selection); + gchar *target_name = gdk_atom_name (target); + gboolean is_target_data = (g_strcmp0 (target_name, GLADE_DND_TARGET_DATA) == 0); + + g_free (target_name); + + if (info == GLADE_DND_INFO_DATA && is_target_data) + { + const guchar *data = gtk_selection_data_get_data (selection); + if (data) + return *((GObject **)data); + } + return NULL; +} + + +void +_glade_dnd_set_data (GtkSelectionData *selection, GObject *data) +{ + static GdkAtom type = 0; + + if (!type) + type = gdk_atom_intern_static_string (GLADE_DND_TARGET_DATA); + + gtk_selection_data_set (selection, type, sizeof (gpointer), + (const guchar *)&data, + sizeof (gpointer)); +} + +static gboolean +on_drag_icon_draw (GtkWidget *widget, cairo_t *cr) +{ + GtkStyleContext *context = gtk_widget_get_style_context (widget); + cairo_pattern_t *gradient; + GtkAllocation alloc; + gint x, y, w, h; + gdouble h2; + GdkRGBA bg; + + /* Not needed according to GtkWidget:draw documentation + * But seems like there is a bug when used as a drag_icon that makes the + * cairo translation used here persist when drawing children. + */ + cairo_save (cr); + + /* Clear BG */ + cairo_set_operator (cr, CAIRO_OPERATOR_CLEAR); + cairo_paint (cr); + cairo_set_operator (cr, CAIRO_OPERATOR_OVER); + + gtk_widget_get_allocation (widget, &alloc); + x = alloc.x; + y = alloc.y; + w = alloc.width; + h = alloc.height; + h2 = h/2.0; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + gtk_style_context_get_background_color (context, gtk_style_context_get_state (context), &bg); + G_GNUC_END_IGNORE_DEPRECATIONS; + + gradient = cairo_pattern_create_linear (x, y, x, y+h); + cairo_pattern_add_color_stop_rgba (gradient, 0, bg.red, bg.green, bg.blue, 0); + cairo_pattern_add_color_stop_rgba (gradient, .5, bg.red, bg.green, bg.blue, .8); + cairo_pattern_add_color_stop_rgba (gradient, 1, bg.red, bg.green, bg.blue, 0); + + cairo_set_source (cr, gradient); + cairo_rectangle (cr, x+h2, y, w-h, h); + cairo_fill (cr); + cairo_pattern_destroy (gradient); + + gradient = cairo_pattern_create_radial (x+h2, y+h2, 0, x+h2, y+h2, h2); + cairo_pattern_add_color_stop_rgba (gradient, 0, bg.red, bg.green, bg.blue, .8); + cairo_pattern_add_color_stop_rgba (gradient, 1, bg.red, bg.green, bg.blue, 0); + + cairo_set_source (cr, gradient); + cairo_rectangle (cr, x, y, h2, h); + cairo_fill (cr); + + cairo_translate (cr, w-h, 0); + cairo_set_source (cr, gradient); + cairo_rectangle (cr, x+h2, y, h2, h); + cairo_fill (cr); + + cairo_pattern_destroy (gradient); + cairo_restore (cr); + + return FALSE; +} + +void +_glade_dnd_set_icon_widget (GdkDragContext *context, + const gchar *icon_name, + const gchar *description) +{ + GtkWidget *window, *box, *label, *icon; + GdkScreen *screen; + GdkVisual *visual; + + screen = gdk_window_get_screen (gdk_drag_context_get_source_window (context)); + window = gtk_window_new (GTK_WINDOW_POPUP); + + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_DND); + gtk_window_set_screen (GTK_WINDOW (window), screen); + + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 4); + gtk_container_set_border_width (GTK_CONTAINER (box), 12); + + icon = gtk_image_new_from_icon_name (icon_name, GTK_ICON_SIZE_BUTTON); + gtk_widget_set_opacity (icon, .8); + + label = gtk_label_new (description); + + gtk_box_pack_start (GTK_BOX (box), icon, FALSE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (box), label, FALSE, TRUE, 0); + + gtk_widget_show_all (box); + gtk_container_add (GTK_CONTAINER (window), box); + + if ((visual = gdk_screen_get_rgba_visual (screen))) + { + gtk_widget_set_visual (window, visual); + gtk_widget_set_app_paintable (window, TRUE); + g_signal_connect (window, "draw", G_CALLBACK (on_drag_icon_draw), NULL); + } + + g_object_ref_sink (window); + gtk_drag_set_icon_widget (context, window, 0, 0); + g_object_unref (window); +} diff --git a/gladeui/glade-dnd.h b/gladeui/glade-dnd.h new file mode 100644 index 0000000..2101618 --- /dev/null +++ b/gladeui/glade-dnd.h @@ -0,0 +1,52 @@ +/* + * glade-dnd.h + * + * Copyright (C) 2013 Juan Pablo Ugarte + * + * Authors: + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GLADE_DND_H__ +#define __GLADE_DND_H__ + +#define GLADE_DND_INFO_DATA 96323 +#define GLADE_DND_TARGET_DATA "glade/x-drag-data" + +#include "glade-drag.h" + +G_BEGIN_DECLS + +GtkTargetEntry *_glade_dnd_get_target (void); + +void _glade_dnd_dest_set (GtkWidget *target); + +void _glade_dnd_set_data (GtkSelectionData *selection, + GObject *data); + +GObject *_glade_dnd_get_data (GdkDragContext *context, + GtkSelectionData *selection, + guint info); + +void _glade_dnd_set_icon_widget (GdkDragContext *context, + const gchar *icon_name, + const gchar *description); + +G_END_DECLS + +#endif /* __GLADE_DND_H__ */ diff --git a/gladeui/glade-drag.c b/gladeui/glade-drag.c new file mode 100644 index 0000000..ef62b72 --- /dev/null +++ b/gladeui/glade-drag.c @@ -0,0 +1,86 @@ +/* + * glade-drag.c + * + * Copyright (C) 2013 Juan Pablo Ugarte + * + * Authors: + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "glade-drag.h" + +G_DEFINE_INTERFACE (_GladeDrag, _glade_drag, G_TYPE_OBJECT) + +static void +_glade_drag_default_init (_GladeDragInterface *iface) +{ +} + +gboolean +_glade_drag_can_drag (_GladeDrag *source) +{ + _GladeDragInterface *iface; + + g_return_val_if_fail (GLADE_IS_DRAG (source), FALSE); + iface = GLADE_DRAG_GET_IFACE (source); + + if (iface->can_drag) + return iface->can_drag (source); + else + return FALSE; +} + +gboolean +_glade_drag_can_drop (_GladeDrag *dest, gint x, gint y, GObject *data) +{ + _GladeDragInterface *iface; + + g_return_val_if_fail (GLADE_IS_DRAG (dest), FALSE); + iface = GLADE_DRAG_GET_IFACE (dest); + + if (iface->can_drop) + return iface->can_drop (dest, x, y, data); + else + return FALSE; +} + +gboolean +_glade_drag_drop (_GladeDrag *dest, gint x, gint y, GObject *data) +{ + _GladeDragInterface *iface; + + g_return_val_if_fail (GLADE_IS_DRAG (dest), FALSE); + iface = GLADE_DRAG_GET_IFACE (dest); + + if (iface->drop) + return iface->drop (dest, x, y, data); + else + return FALSE; +} + +void +_glade_drag_highlight (_GladeDrag *dest, gint x, gint y) +{ + _GladeDragInterface *iface; + + g_return_if_fail (GLADE_IS_DRAG (dest)); + iface = GLADE_DRAG_GET_IFACE (dest); + + if (iface->highlight) + iface->highlight (dest, x, y); +} diff --git a/gladeui/glade-drag.h b/gladeui/glade-drag.h new file mode 100644 index 0000000..de3843e --- /dev/null +++ b/gladeui/glade-drag.h @@ -0,0 +1,74 @@ +/* + * glade-drag.h + * + * Copyright (C) 2013 Juan Pablo Ugarte + * + * Authors: + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _GLADE_DRAG_H_ +#define _GLADE_DRAG_H_ + +#include "glade-widget-adaptor.h" + +G_BEGIN_DECLS + +#define GLADE_TYPE_DRAG _glade_drag_get_type () +G_DECLARE_INTERFACE (_GladeDrag, _glade_drag, GLADE, DRAG, GObject) + +struct __GladeDragInterface +{ + GTypeInterface parent_instance; + + gboolean (*can_drag) (_GladeDrag *source); + + gboolean (*can_drop) (_GladeDrag *dest, + gint x, + gint y, + GObject *data); + + gboolean (*drop) (_GladeDrag *dest, + gint x, + gint y, + GObject *data); + + void (*highlight) (_GladeDrag *dest, + gint x, + gint y); +}; + +gboolean _glade_drag_can_drag (_GladeDrag *source); + +gboolean _glade_drag_can_drop (_GladeDrag *dest, + gint x, + gint y, + GObject *data); + +gboolean _glade_drag_drop (_GladeDrag *dest, + gint x, + gint y, + GObject *data); + +void _glade_drag_highlight (_GladeDrag *dest, + gint x, + gint y); + +G_END_DECLS + +#endif /* _GLADE_DRAG_DEST_H_ */ diff --git a/gladeui/glade-editable.c b/gladeui/glade-editable.c new file mode 100644 index 0000000..6382988 --- /dev/null +++ b/gladeui/glade-editable.c @@ -0,0 +1,221 @@ +/* + * glade-editable.c + * + * Copyright (C) 2008 Tristan Van Berkom. + * + * Authors: + * Tristan Van Berkom + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include +#include + +#include "glade-project.h" +#include "glade-widget.h" +#include "glade-editable.h" + +G_DEFINE_INTERFACE (GladeEditable, glade_editable, GTK_TYPE_WIDGET) + +static GQuark glade_editable_project_quark = 0; +static GQuark glade_editable_widget_quark = 0; +static GQuark glade_editable_loading_quark = 0; +static GQuark glade_editable_destroy_quark = 0; + +static void +project_changed (GladeProject *project, + GladeCommand *command, + gboolean execute, + GladeEditable *editable) +{ + GladeWidget *widget; + + widget = g_object_get_qdata (G_OBJECT (editable), glade_editable_widget_quark); + + glade_editable_load (editable, widget); +} + +static void +project_closed (GladeProject *project, + GladeEditable *editable) +{ + glade_editable_load (editable, NULL); +} + +static void +editable_destroyed (GladeEditable *editable) +{ + glade_editable_load (editable, NULL); +} + +static void +glade_editable_load_default (GladeEditable *editable, + GladeWidget *widget) +{ + GladeWidget *old_widget; + GladeProject *old_project; + + old_widget = g_object_get_qdata (G_OBJECT (editable), glade_editable_widget_quark); + old_project = g_object_get_qdata (G_OBJECT (editable), glade_editable_project_quark); + + if (old_widget != widget) + { + if (old_project) + { + g_signal_handlers_disconnect_by_func (old_project, G_CALLBACK (project_changed), editable); + g_signal_handlers_disconnect_by_func (old_project, G_CALLBACK (project_closed), editable); + + g_object_set_qdata (G_OBJECT (editable), glade_editable_widget_quark, NULL); + g_object_set_qdata (G_OBJECT (editable), glade_editable_project_quark, NULL); + } + + if (widget) + { + GladeProject *project = glade_widget_get_project (widget); + + g_object_set_qdata (G_OBJECT (editable), glade_editable_widget_quark, widget); + g_object_set_qdata (G_OBJECT (editable), glade_editable_project_quark, project); + + g_signal_connect (project, "changed", + G_CALLBACK (project_changed), editable); + g_signal_connect (project, "close", + G_CALLBACK (project_closed), editable); + } + } +} + +static void +glade_editable_default_init (GladeEditableInterface *iface) +{ + glade_editable_project_quark = g_quark_from_static_string ("glade-editable-project-quark"); + glade_editable_widget_quark = g_quark_from_static_string ("glade-editable-widget-quark"); + glade_editable_loading_quark = g_quark_from_static_string ("glade-editable-loading-quark"); + glade_editable_destroy_quark = g_quark_from_static_string ("glade-editable-destroy-quark"); + + iface->load = glade_editable_load_default; +} + +/** + * glade_editable_load: + * @editable: A #GladeEditable + * @widget: the #GladeWidget to load + * + * Loads @widget property values into @editable + * (the editable will watch the widgets properties + * until its loaded with another widget or %NULL) + */ +void +glade_editable_load (GladeEditable *editable, GladeWidget *widget) +{ + GladeEditableInterface *iface; + g_return_if_fail (GLADE_IS_EDITABLE (editable)); + g_return_if_fail (widget == NULL || GLADE_IS_WIDGET (widget)); + + /* Connect to the destroy signal once, make sure we unload the widget and disconnect + * to any signals when destroying + */ + if (!GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (editable), glade_editable_destroy_quark))) + { + g_signal_connect (editable, "destroy", G_CALLBACK (editable_destroyed), NULL); + g_object_set_qdata (G_OBJECT (editable), glade_editable_destroy_quark, GINT_TO_POINTER (TRUE)); + } + + iface = GLADE_EDITABLE_GET_IFACE (editable); + + g_object_set_qdata (G_OBJECT (editable), glade_editable_loading_quark, GINT_TO_POINTER (TRUE)); + + if (iface->load) + iface->load (editable, widget); + else + g_critical ("No GladeEditable::load() support on type %s", + G_OBJECT_TYPE_NAME (editable)); + + g_object_set_qdata (G_OBJECT (editable), glade_editable_loading_quark, GINT_TO_POINTER (FALSE)); +} + +/** + * glade_editable_set_show_name: + * @editable: A #GladeEditable + * @show_name: Whether or not to show the name entry + * + * This only applies for the general page in the property + * editor, editables that embed the #GladeEditorTable implementation + * for the general page should use this to forward the message + * to its embedded editable. + */ +void +glade_editable_set_show_name (GladeEditable *editable, gboolean show_name) +{ + GladeEditableInterface *iface; + g_return_if_fail (GLADE_IS_EDITABLE (editable)); + + iface = GLADE_EDITABLE_GET_IFACE (editable); + + if (iface->set_show_name) + iface->set_show_name (editable, show_name); +} + +/** + * glade_editable_loaded_widget: + * @editable: A #GladeEditable + * + * Returns: (transfer none) (nullable): a #GladeWidget or %NULL if the editable hasn't been loaded + */ +GladeWidget * +glade_editable_loaded_widget (GladeEditable *editable) +{ + return g_object_get_qdata (G_OBJECT (editable), glade_editable_widget_quark); +} + +gboolean +glade_editable_loading (GladeEditable *editable) +{ + return GPOINTER_TO_INT (g_object_get_qdata (G_OBJECT (editable), glade_editable_loading_quark)); +} + +void +glade_editable_block (GladeEditable *editable) +{ + GladeProject *project; + + g_return_if_fail (GLADE_IS_EDITABLE (editable)); + + project = g_object_get_qdata (G_OBJECT (editable), glade_editable_project_quark); + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + g_signal_handlers_block_by_func (project, G_CALLBACK (project_changed), editable); +} + +void +glade_editable_unblock (GladeEditable *editable) +{ + GladeProject *project; + + g_return_if_fail (GLADE_IS_EDITABLE (editable)); + + project = g_object_get_qdata (G_OBJECT (editable), glade_editable_project_quark); + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + g_signal_handlers_unblock_by_func (project, G_CALLBACK (project_changed), editable); +} diff --git a/gladeui/glade-editable.h b/gladeui/glade-editable.h new file mode 100644 index 0000000..509a693 --- /dev/null +++ b/gladeui/glade-editable.h @@ -0,0 +1,46 @@ +#ifndef __GLADE_EDITABLE_H__ +#define __GLADE_EDITABLE_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_EDITABLE glade_editable_get_type () +G_DECLARE_INTERFACE (GladeEditable, glade_editable, GLADE, EDITABLE, GtkWidget) + +typedef enum +{ + GLADE_PAGE_GENERAL, + GLADE_PAGE_COMMON, + GLADE_PAGE_PACKING, + GLADE_PAGE_ATK, + GLADE_PAGE_QUERY, + GLADE_PAGE_SIGNAL +} GladeEditorPageType; + + +struct _GladeEditableInterface +{ + GTypeInterface g_iface; + + /* virtual table */ + void (* load) (GladeEditable *editable, + GladeWidget *widget); + void (* set_show_name) (GladeEditable *editable, + gboolean show_name); +}; + +void glade_editable_load (GladeEditable *editable, + GladeWidget *widget); +void glade_editable_set_show_name (GladeEditable *editable, + gboolean show_name); +GladeWidget *glade_editable_loaded_widget (GladeEditable *editable); +gboolean glade_editable_loading (GladeEditable *editable); + +void glade_editable_block (GladeEditable *editable); +void glade_editable_unblock (GladeEditable *editable); + +G_END_DECLS + +#endif /* __GLADE_EDITABLE_H__ */ diff --git a/gladeui/glade-editor-property.c b/gladeui/glade-editor-property.c new file mode 100644 index 0000000..cdaa5d8 --- /dev/null +++ b/gladeui/glade-editor-property.c @@ -0,0 +1,3859 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/** + * SECTION:glade-editor-property + * @Short_Description: A generic widget to edit a #GladeProperty. + * + * The #GladeEditorProperty is a factory that will create the correct + * control for the #GladePropertyDef it was created for and provides + * a simple unified api to them. + */ + +#include +#include +#include +#include +#include +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-editable.h" +#include "glade-editor-property.h" +#include "glade-property-label.h" +#include "glade-property.h" +#include "glade-command.h" +#include "glade-project.h" +#include "glade-popup.h" +#include "glade-builtins.h" +#include "glade-marshallers.h" +#include "glade-displayable-values.h" +#include "glade-named-icon-chooser-dialog.h" +#include "glade-private.h" + +enum +{ + PROP_0, + PROP_PROPERTY_DEFINITION, + PROP_USE_COMMAND, + PROP_DISABLE_CHECK, + PROP_CUSTOM_TEXT +}; + +enum +{ + CHANGED, + COMMIT, + LAST_SIGNAL +}; + +static GtkTableClass *table_class; +static GladeEditorPropertyClass *editor_property_class; + +static guint glade_eprop_signals[LAST_SIGNAL] = { 0, }; + +#define GLADE_PROPERTY_TABLE_ROW_SPACING 2 +#define FLAGS_COLUMN_SETTING 0 +#define FLAGS_COLUMN_SYMBOL 1 +#define FLAGS_COLUMN_VALUE 2 + +typedef struct _GladeEditorPropertyPrivate +{ + GladePropertyDef *property_def; /* The property definition this GladeEditorProperty was created for */ + GladeProperty *property; /* The currently loaded property */ + + GtkWidget *item_label; /* A GladePropertyLabel, if one was constructed */ + GtkWidget *input; /* Input part of property (need to set sensitivity seperately) */ + GtkWidget *check; /* Check button for optional properties. */ + + gulong tooltip_id; /* signal connection id for tooltip changes */ + gulong sensitive_id; /* signal connection id for sensitivity changes */ + gulong changed_id; /* signal connection id for value changes */ + gulong enabled_id; /* signal connection id for enable/disable changes */ + + gchar *custom_text; /* Custom text to display in the property label */ + + guint loading : 1; /* True during glade_editor_property_load calls, this + * is used to avoid feedback from input widgets. + */ + guint committing : 1; /* True while the editor property itself is applying + * the property with glade_editor_property_commit_no_callback (). + */ + guint use_command : 1; /* Whether we should use the glade command interface + * or skip directly to GladeProperty interface. + * (used for query dialogs). + */ + guint changed_blocked : 1; /* Whether the GladeProperty changed signal is currently blocked */ + + guint disable_check : 1; /* Whether to explicitly disable the optional check button */ +} GladeEditorPropertyPrivate; + +static void glade_editor_property_editable_init (GladeEditableInterface *iface); + +static GladeEditableInterface *parent_editable_iface; + +G_DEFINE_TYPE_WITH_CODE (GladeEditorProperty, glade_editor_property, GTK_TYPE_BOX, + G_ADD_PRIVATE (GladeEditorProperty) + G_IMPLEMENT_INTERFACE (GLADE_TYPE_EDITABLE, + glade_editor_property_editable_init)) + + +/******************************************************************************* + * GladeEditableInterface * + *******************************************************************************/ +static void +glade_editor_property_editable_load (GladeEditable *editable, + GladeWidget *widget) +{ + /* Chain up to default implementation */ + parent_editable_iface->load (editable, widget); + + glade_editor_property_load_by_widget (GLADE_EDITOR_PROPERTY (editable), widget); +} + +static void +glade_editor_property_set_show_name (GladeEditable *editable, gboolean show_name) +{ +} + +static void +glade_editor_property_editable_init (GladeEditableInterface *iface) +{ + parent_editable_iface = g_type_default_interface_peek (GLADE_TYPE_EDITABLE); + + iface->load = glade_editor_property_editable_load; + iface->set_show_name = glade_editor_property_set_show_name; +} + +/******************************************************************************* + GladeEditorPropertyClass + *******************************************************************************/ + +static void +deepest_child_grab_focus (GtkWidget *widget, gpointer data) +{ + gboolean *focus_set = data; + + if (*focus_set) + return; + + if (GTK_IS_CONTAINER (widget)) + gtk_container_foreach (GTK_CONTAINER (widget), + deepest_child_grab_focus, + data); + + if (gtk_widget_get_can_focus (widget)) + { + gtk_widget_grab_focus (widget); + *focus_set = TRUE; + } +} + +/* declare this forwardly for the finalize routine */ +static void glade_editor_property_load_common (GladeEditorProperty *eprop, + GladeProperty *property); + +static void +glade_editor_property_commit_common (GladeEditorProperty *eprop, + GValue *value) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + if (priv->use_command == FALSE) + glade_property_set_value (priv->property, value); + else + glade_command_set_property_value (priv->property, value); + + /* If the value was denied by a verify function, we'll have to + * reload the real value. + */ + if (!glade_property_equals_value (priv->property, value)) + glade_editor_property_load (eprop, priv->property); + + /* Restore input focus. If the property is construct-only, then + * glade_widget_rebuild() will be called which means the object will be + * removed from the project/selection and a new one will be added, which makes + * the eprop loose its focus. + * + * FIXME: find a better way to set focus? + * make gtk_widget_grab_focus(priv->input) work? + */ + if (glade_property_def_get_construct_only (priv->property_def)) + { + gboolean focus_set = FALSE; + deepest_child_grab_focus (priv->input, &focus_set); + } +} + +void +glade_editor_property_commit_no_callback (GladeEditorProperty *eprop, + GValue *value) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop)); + + if (priv->committing) + return; + + g_signal_handler_block (G_OBJECT (priv->property), priv->changed_id); + priv->changed_blocked = TRUE; + + priv->committing = TRUE; + glade_editor_property_commit (eprop, value); + priv->committing = FALSE; + + /* When construct-only properties are set, we are disconnected and re-connected + * to the GladeWidget while it's rebuilding it's instance, in this case the + * signal handler is no longer blocked at this point. + */ + if (priv->changed_blocked) + g_signal_handler_unblock (G_OBJECT (priv->property), priv->changed_id); +} + + +void +glade_editor_property_set_custom_text (GladeEditorProperty *eprop, + const gchar *custom_text) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop)); + + if (g_strcmp0 (priv->custom_text, custom_text) != 0) + { + g_free (priv->custom_text); + priv->custom_text = g_strdup (custom_text); + + if (priv->item_label) + glade_property_label_set_custom_text (GLADE_PROPERTY_LABEL (priv->item_label), + custom_text); + + g_object_notify (G_OBJECT (eprop), "custom-text"); + } +} + +const gchar * +glade_editor_property_get_custom_text (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_val_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop), NULL); + + return priv->custom_text; +} + +void +glade_editor_property_set_disable_check (GladeEditorProperty *eprop, + gboolean disable_check) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop)); + + if (priv->disable_check != disable_check) + { + priv->disable_check = disable_check; + gtk_widget_set_visible (priv->check, !disable_check); + g_object_notify (G_OBJECT (eprop), "disable-check"); + } +} + +gboolean +glade_editor_property_get_disable_check (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_val_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop), FALSE); + + return priv->disable_check; +} + +/** + * glade_editor_property_get_item_label: + * @eprop: a #GladeEditorProperty + * + * Returns: (transfer none): the #GladePropertyLabel + */ +GtkWidget * +glade_editor_property_get_item_label (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_val_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop), NULL); + + if (!priv->item_label) + { + priv->item_label = glade_property_label_new(); + + g_object_ref_sink (priv->item_label); + + if (priv->property) + glade_property_label_set_property (GLADE_PROPERTY_LABEL (priv->item_label), + priv->property); + } + + return priv->item_label; +} + +/** + * glade_editor_property_get_property_def: + * @eprop: a #GladeEditorProperty + * + * Returns: (transfer none): the #GladePropertyDef + */ +GladePropertyDef * +glade_editor_property_get_property_def (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_val_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop), NULL); + + return priv->property_def; +} + +/** + * glade_editor_property_get_property: + * @eprop: a #GladeEditorProperty + * + * Returns: (transfer none): the #GladeProperty + */ +GladeProperty * +glade_editor_property_get_property (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_val_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop), NULL); + + return priv->property; +} + +gboolean +glade_editor_property_loading (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_val_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop), FALSE); + + return priv->loading; +} + +static void +glade_editor_property_tooltip_cb (GladeProperty *property, + const gchar *tooltip, + const gchar *insensitive, + const gchar *support, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + const gchar *choice_tooltip; + + if (glade_property_get_sensitive (property)) + choice_tooltip = tooltip; + else + choice_tooltip = insensitive; + + gtk_widget_set_tooltip_text (priv->input, choice_tooltip); +} + +static void +glade_editor_property_sensitivity_cb (GladeProperty *property, + GParamSpec *pspec, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gboolean property_enabled = glade_property_get_enabled (property); + gboolean sensitive = glade_property_get_sensitive (priv->property); + gboolean support_sensitive = + (glade_property_get_state (priv->property) & GLADE_STATE_SUPPORT_DISABLED) == 0; + + gtk_widget_set_sensitive (priv->input, + sensitive && support_sensitive && property_enabled); + + if (priv->check) + gtk_widget_set_sensitive (priv->check, sensitive && support_sensitive); +} + +static void +glade_editor_property_value_changed_cb (GladeProperty *property, + GValue *old_value, + GValue *value, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_assert (priv->property == property); + + glade_editor_property_load (eprop, priv->property); +} + +static void +glade_editor_property_enabled_toggled_cb (GtkWidget *check, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + glade_command_set_property_enabled (priv->property, + gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (check))); +} + +static void +glade_editor_property_enabled_cb (GladeProperty *property, + GParamSpec *pspec, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gboolean enabled; + + g_assert (priv->property == property); + + if (glade_property_def_optional (priv->property_def)) + { + enabled = glade_property_get_enabled (property); + + /* sensitive = enabled && support enabled && sensitive */ + if (enabled == FALSE) + gtk_widget_set_sensitive (priv->input, FALSE); + else if (glade_property_get_sensitive (property) || + (glade_property_get_state (property) & GLADE_STATE_SUPPORT_DISABLED) != 0) + gtk_widget_set_sensitive (priv->input, TRUE); + + g_signal_handlers_block_by_func (priv->check, glade_editor_property_enabled_toggled_cb, eprop); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->check), enabled); + g_signal_handlers_unblock_by_func (priv->check, glade_editor_property_enabled_toggled_cb, eprop); + } +} + +static gboolean +glade_editor_property_button_pressed (GtkWidget *widget, + GdkEventButton *event, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + if (glade_popup_is_popup_event (event)) + { + glade_popup_property_pop (priv->property, event); + return TRUE; + } + return FALSE; +} + + +static void +glade_editor_property_constructed (GObject *object) +{ + GladeEditorProperty *eprop; + GladeEditorPropertyPrivate *priv; + + eprop = GLADE_EDITOR_PROPERTY (object); + priv = glade_editor_property_get_instance_private (eprop); + + G_OBJECT_CLASS (glade_editor_property_parent_class)->constructed (object); + + /* Create hbox and possibly check button + */ + if (glade_property_def_optional (priv->property_def)) + { + priv->check = gtk_check_button_new (); + gtk_widget_set_focus_on_click (priv->check, FALSE); + + if (!priv->disable_check) + gtk_widget_show (priv->check); + + gtk_box_pack_start (GTK_BOX (eprop), priv->check, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (priv->check), "toggled", + G_CALLBACK (glade_editor_property_enabled_toggled_cb), + eprop); + } + + /* Create the class specific input widget and add it */ + priv->input = GLADE_EDITOR_PROPERTY_GET_CLASS (eprop)->create_input (eprop); + gtk_widget_show (priv->input); + + g_signal_connect (G_OBJECT (priv->input), "button-press-event", + G_CALLBACK (glade_editor_property_button_pressed), eprop); + + if (gtk_widget_get_halign (priv->input) != GTK_ALIGN_FILL) + gtk_box_pack_start (GTK_BOX (eprop), priv->input, FALSE, TRUE, 0); + else + gtk_box_pack_start (GTK_BOX (eprop), priv->input, TRUE, TRUE, 0); +} + +static void +glade_editor_property_finalize (GObject *object) +{ + GladeEditorProperty *eprop = GLADE_EDITOR_PROPERTY (object); + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + /* detouch from loaded property */ + glade_editor_property_load_common (eprop, NULL); + + g_free (priv->custom_text); + + G_OBJECT_CLASS (table_class)->finalize (object); +} + +static void +glade_editor_property_dispose (GObject *object) +{ + GladeEditorProperty *eprop = GLADE_EDITOR_PROPERTY (object); + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_clear_object (&priv->item_label); + + G_OBJECT_CLASS (table_class)->dispose (object); +} + +static void +glade_editor_property_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeEditorProperty *eprop = GLADE_EDITOR_PROPERTY (object); + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + switch (prop_id) + { + case PROP_PROPERTY_DEFINITION: + priv->property_def = g_value_get_pointer (value); + break; + case PROP_USE_COMMAND: + priv->use_command = g_value_get_boolean (value); + break; + case PROP_DISABLE_CHECK: + glade_editor_property_set_disable_check (eprop, g_value_get_boolean (value)); + break; + case PROP_CUSTOM_TEXT: + glade_editor_property_set_custom_text (eprop, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_editor_property_real_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeEditorProperty *eprop = GLADE_EDITOR_PROPERTY (object); + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + switch (prop_id) + { + case PROP_PROPERTY_DEFINITION: + g_value_set_pointer (value, priv->property_def); + break; + case PROP_USE_COMMAND: + g_value_set_boolean (value, priv->use_command); + break; + case PROP_DISABLE_CHECK: + g_value_set_boolean (value, priv->disable_check); + break; + case PROP_CUSTOM_TEXT: + g_value_set_string (value, priv->custom_text); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_eprop_property_finalized (GladeEditorProperty *eprop, + GladeProperty *where_property_was) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + priv->tooltip_id = 0; + priv->sensitive_id = 0; + priv->changed_id = 0; + priv->enabled_id = 0; + priv->property = NULL; + + glade_editor_property_load (eprop, NULL); +} + +static void +glade_editor_property_load_common (GladeEditorProperty *eprop, + GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + /* NOTE THIS CODE IS FINALIZE SAFE */ + + /* disconnect anything from previously loaded property */ + if (priv->property != property && priv->property != NULL) + { + if (priv->tooltip_id > 0) + g_signal_handler_disconnect (priv->property, priv->tooltip_id); + if (priv->sensitive_id > 0) + g_signal_handler_disconnect (priv->property, priv->sensitive_id); + if (priv->changed_id > 0) + g_signal_handler_disconnect (priv->property, priv->changed_id); + if (priv->enabled_id > 0) + g_signal_handler_disconnect (priv->property, priv->enabled_id); + + priv->tooltip_id = 0; + priv->sensitive_id = 0; + priv->changed_id = 0; + priv->enabled_id = 0; + priv->changed_blocked = FALSE; + + /* Unref it here */ + g_object_weak_unref (G_OBJECT (priv->property), + (GWeakNotify) glade_eprop_property_finalized, eprop); + + + /* For a reason I cant quite tell yet, this is the only + * safe way to nullify the property member of the eprop + * without leeking signal connections to properties :-/ + */ + if (property == NULL) + { + priv->property = NULL; + } + } + + /* Connect new stuff, deal with tooltip + */ + if (priv->property != property && property != NULL) + { + GladePropertyDef *pclass = glade_property_get_def (property); + + priv->property = property; + + priv->tooltip_id = + g_signal_connect (G_OBJECT (priv->property), + "tooltip-changed", + G_CALLBACK (glade_editor_property_tooltip_cb), + eprop); + priv->sensitive_id = + g_signal_connect (G_OBJECT (priv->property), + "notify::sensitive", + G_CALLBACK (glade_editor_property_sensitivity_cb), + eprop); + priv->changed_id = + g_signal_connect (G_OBJECT (priv->property), + "value-changed", + G_CALLBACK (glade_editor_property_value_changed_cb), + eprop); + priv->enabled_id = + g_signal_connect (G_OBJECT (priv->property), + "notify::enabled", + G_CALLBACK (glade_editor_property_enabled_cb), + eprop); + + /* In query dialogs when the user hits cancel, + * these babies go away (so better stay protected). + */ + g_object_weak_ref (G_OBJECT (priv->property), + (GWeakNotify) glade_eprop_property_finalized, eprop); + + /* Load initial tooltips + */ + glade_editor_property_tooltip_cb + (property, glade_property_def_get_tooltip (pclass), + glade_propert_get_insensitive_tooltip (property), + glade_property_get_support_warning (property), eprop); + + /* Load initial enabled state + */ + glade_editor_property_enabled_cb (property, NULL, eprop); + + /* Load initial sensitive state. + */ + glade_editor_property_sensitivity_cb (property, NULL, eprop); + } +} + +static void +glade_editor_property_init (GladeEditorProperty *eprop) +{ + gtk_box_set_spacing (GTK_BOX (eprop), 4); + gtk_orientable_set_orientation (GTK_ORIENTABLE (eprop), + GTK_ORIENTATION_HORIZONTAL); +} + +static void +glade_editor_property_class_init (GladeEditorPropertyClass *eprop_class) +{ + GObjectClass *object_class; + g_return_if_fail (eprop_class != NULL); + + /* Both parent classes assigned here. + */ + editor_property_class = eprop_class; + table_class = g_type_class_peek_parent (eprop_class); + object_class = G_OBJECT_CLASS (eprop_class); + + /* GObjectClass */ + object_class->constructed = glade_editor_property_constructed; + object_class->finalize = glade_editor_property_finalize; + object_class->dispose = glade_editor_property_dispose; + object_class->get_property = glade_editor_property_real_get_property; + object_class->set_property = glade_editor_property_set_property; + + /* Class methods */ + eprop_class->load = glade_editor_property_load_common; + eprop_class->commit = glade_editor_property_commit_common; + eprop_class->create_input = NULL; + + + /** + * GladeEditorProperty::value-changed: + * @gladeeditorproperty: the #GladeEditorProperty which changed value + * @arg1: the #GladeProperty that's value changed. + * + * Emitted when a contained property changes value + */ + glade_eprop_signals[CHANGED] = + g_signal_new ("value-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeEditorPropertyClass, changed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, GLADE_TYPE_PROPERTY); + + /** + * GladeEditorProperty::commit: + * @gladeeditorproperty: the #GladeEditorProperty which changed value + * @arg1: the new #GValue to commit. + * + * Emitted when a property's value is committed, can be useful to serialize + * commands before and after the property's commit command from custom editors. + */ + glade_eprop_signals[COMMIT] = + g_signal_new ("commit", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeEditorPropertyClass, commit), + NULL, NULL, + _glade_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + /* Properties */ + g_object_class_install_property + (object_class, PROP_PROPERTY_DEFINITION, + g_param_spec_pointer + ("property-def", _("Property Definition"), + _("The GladePropertyDef this GladeEditorProperty was created for"), + G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (object_class, PROP_USE_COMMAND, + g_param_spec_boolean + ("use-command", _("Use Command"), + _("Whether we should use the command API for the undo/redo stack"), + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property + (object_class, PROP_DISABLE_CHECK, + g_param_spec_boolean + ("disable-check", _("Disable Check"), + _("Whether to explicitly disable the check button"), + FALSE, G_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + + g_object_class_install_property + (object_class, PROP_CUSTOM_TEXT, + g_param_spec_string + ("custom-text", _("Custom Text"), + _("Custom Text to display in the property label"), + NULL, G_PARAM_READWRITE)); +} + +/******************************************************************************* + GladeEditorPropertyNumericClass + *******************************************************************************/ +struct _GladeEPropNumeric +{ + GladeEditorProperty parent_instance; + + GtkWidget *spin; + GBinding *binding; + + gboolean refreshing; +}; + +GLADE_MAKE_EPROP (GladeEPropNumeric, glade_eprop_numeric, GLADE, EPROP_NUMERIC) + +static void +glade_eprop_numeric_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_numeric_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gdouble val = 0.0F; + GladeEPropNumeric *eprop_numeric = GLADE_EPROP_NUMERIC (eprop); + GParamSpec *pspec; + GValue *value; + + if (eprop_numeric->refreshing) + return; + + /* Chain up first */ + editor_property_class->load (eprop, property); + + g_clear_object (&eprop_numeric->binding); + + if (property) + { + value = glade_property_inline_value (property); + pspec = glade_property_def_get_pspec (priv->property_def); + + if (G_IS_PARAM_SPEC_INT (pspec)) + val = g_value_get_int (value); + else if (G_IS_PARAM_SPEC_UINT (pspec)) + val = g_value_get_uint (value); + else if (G_IS_PARAM_SPEC_LONG (pspec)) + val = g_value_get_long (value); + else if (G_IS_PARAM_SPEC_ULONG (pspec)) + val = g_value_get_ulong (value); + else if (G_IS_PARAM_SPEC_INT64 (pspec)) + val = g_value_get_int64 (value); + else if (G_IS_PARAM_SPEC_UINT64 (pspec)) + val = g_value_get_uint64 (value); + else if (G_IS_PARAM_SPEC_DOUBLE (pspec)) + val = g_value_get_double (value); + else if (G_IS_PARAM_SPEC_FLOAT (pspec)) + val = g_value_get_float (value); + else + g_warning ("Unsupported type %s\n", + g_type_name (G_PARAM_SPEC_TYPE (pspec))); + gtk_spin_button_set_value (GTK_SPIN_BUTTON (eprop_numeric->spin), val); + + if (G_IS_PARAM_SPEC_FLOAT (pspec) || G_IS_PARAM_SPEC_DOUBLE (pspec)) + eprop_numeric->binding = g_object_bind_property (property, + "precision", + eprop_numeric->spin, + "digits", + G_BINDING_SYNC_CREATE); + } +} + +#define NEAREST_INT_CAST(x) (((x - floor (x) < ceil (x) - x)) ? floor (x) : ceil (x)) + +static void +glade_eprop_numeric_value_set (GValue *val, gdouble value) +{ + if (G_VALUE_HOLDS_INT (val)) + g_value_set_int (val, NEAREST_INT_CAST (value)); + else if (G_VALUE_HOLDS_UINT (val)) + g_value_set_uint (val, NEAREST_INT_CAST (value)); + else if (G_VALUE_HOLDS_LONG (val)) + g_value_set_long (val, NEAREST_INT_CAST (value)); + else if (G_VALUE_HOLDS_ULONG (val)) + g_value_set_ulong (val, NEAREST_INT_CAST (value)); + else if (G_VALUE_HOLDS_INT64 (val)) + g_value_set_int64 (val, NEAREST_INT_CAST (value)); + else if (G_VALUE_HOLDS_UINT64 (val)) + g_value_set_uint64 (val, NEAREST_INT_CAST (value)); + else if (G_VALUE_HOLDS_FLOAT (val)) + g_value_set_float (val, value); + else if (G_VALUE_HOLDS_DOUBLE (val)) + g_value_set_double (val, value); + else + g_warning ("Unsupported type %s\n", G_VALUE_TYPE_NAME (val)); +} + +static void +glade_eprop_numeric_changed (GtkWidget *spin, GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GValue val = { 0, }; + GParamSpec *pspec; + + if (priv->loading) + return; + + pspec = glade_property_def_get_pspec (priv->property_def); + g_value_init (&val, pspec->value_type); + glade_eprop_numeric_value_set (&val, gtk_spin_button_get_value (GTK_SPIN_BUTTON (spin))); + + glade_editor_property_commit_no_callback (eprop, &val); + g_value_unset (&val); +} + +static void +glade_eprop_numeric_force_update (GtkSpinButton *spin, + GladeEditorProperty *eprop) +{ + GladeProperty *prop = glade_editor_property_get_property (eprop); + GladePropertyDef *property_def = glade_property_get_def (prop); + GValue *val, newval = G_VALUE_INIT; + gdouble value; + gchar *text; + + text = gtk_editable_get_chars (GTK_EDITABLE (spin), 0, -1); + + /* + * Skip empty strings, otherwise if 0 is out of range the spin will get a + * bogus update. + */ + if (text && *text == '\0') + return; + + val = glade_property_inline_value (prop); + + g_value_init (&newval, G_VALUE_TYPE (val)); + value = g_strtod (text, NULL); + glade_eprop_numeric_value_set (&newval, value); + + /* + * If we unconditionally update the spin button whenever + * the entry changes we get bogus results (notably, the + * updating the spin button will insert 0 whenever text + * is removed, so selecting and inserting text will have + * an appended 0). + */ + if (glade_property_def_compare (property_def, val, &newval)) + { + gdouble min, max; + + gtk_spin_button_get_range (spin, &min, &max); + + if (value < min || value > max) + { + /* Special case, if the value is out of range, we force an update to + * change the value in the spin so the user knows about the range issue. + */ + gtk_spin_button_update (spin); + } + else + { + /* Here we commit the new property value but we make sure + * glade_eprop_numeric_load() is not called to prevent + * gtk_spin_button_set_value() changing the value the + * user is trying to input. + */ + GladeEPropNumeric *eprop_numeric = GLADE_EPROP_NUMERIC (eprop); + eprop_numeric->refreshing = TRUE; + glade_editor_property_commit_no_callback (eprop, &newval); + eprop_numeric->refreshing = FALSE; + } + } + + g_value_unset (&newval); + g_free (text); +} + +static void +on_spin_digits_notify (GObject *gobject, GParamSpec *pspec, gpointer user_data) +{ + gint digits = gtk_spin_button_get_digits (GTK_SPIN_BUTTON (gobject)); + gdouble step = 1.0 / pow (10, digits); + + gtk_spin_button_set_increments (GTK_SPIN_BUTTON (gobject), step, step * 10); +} + +static GtkWidget * +glade_eprop_numeric_create_input (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropNumeric *eprop_numeric = GLADE_EPROP_NUMERIC (eprop); + GtkAdjustment *adjustment; + GParamSpec *pspec; + + pspec = glade_property_def_get_pspec (priv->property_def); + adjustment = glade_property_def_make_adjustment (priv->property_def); + eprop_numeric->spin = + gtk_spin_button_new (adjustment, 0.01, + G_IS_PARAM_SPEC_FLOAT (pspec) || + G_IS_PARAM_SPEC_DOUBLE (pspec) ? 2 : 0); + gtk_widget_set_hexpand (eprop_numeric->spin, TRUE); + gtk_widget_set_halign (eprop_numeric->spin, GTK_ALIGN_FILL); + gtk_widget_set_valign (eprop_numeric->spin, GTK_ALIGN_CENTER); + + gtk_entry_set_activates_default (GTK_ENTRY (eprop_numeric->spin), TRUE); + gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (eprop_numeric->spin), TRUE); + g_signal_connect (eprop_numeric->spin, "notify::digits", + G_CALLBACK (on_spin_digits_notify), NULL); + + glade_util_remove_scroll_events (eprop_numeric->spin); + gtk_widget_show (eprop_numeric->spin); + + /* Limit the size of the spin if max allowed value is too big */ + if (gtk_adjustment_get_upper (adjustment) > 9999999999999999.0) + gtk_entry_set_width_chars (GTK_ENTRY (eprop_numeric->spin), 16); + + /* The force update callback is here to ensure that whenever the value + * is modified, it's committed immediately without requiring entry activation + * (this avoids lost modifications when modifying a value and navigating away) + */ + g_signal_connect (G_OBJECT (eprop_numeric->spin), "changed", + G_CALLBACK (glade_eprop_numeric_force_update), eprop); + + g_signal_connect (G_OBJECT (eprop_numeric->spin), "value-changed", + G_CALLBACK (glade_eprop_numeric_changed), eprop); + + return eprop_numeric->spin; +} + +/******************************************************************************* + GladeEditorPropertyEnumClass + *******************************************************************************/ +struct _GladeEPropEnum +{ + GladeEditorProperty parent_instance; + + GtkWidget *combo_box; +}; + +GLADE_MAKE_EPROP (GladeEPropEnum, glade_eprop_enum, GLADE, EPROP_ENUM) + +static void +glade_eprop_enum_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_enum_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropEnum *eprop_enum = GLADE_EPROP_ENUM (eprop); + GParamSpec *pspec; + GEnumClass *eclass; + guint i; + gint value; + + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property) + { + pspec = glade_property_def_get_pspec (priv->property_def); + eclass = g_type_class_ref (pspec->value_type); + value = g_value_get_enum (glade_property_inline_value (property)); + + for (i = 0; i < eclass->n_values; i++) + if (eclass->values[i].value == value) + break; + + gtk_combo_box_set_active (GTK_COMBO_BOX (eprop_enum->combo_box), + i < eclass->n_values ? i : 0); + g_type_class_unref (eclass); + } +} + +static void +glade_eprop_enum_changed (GtkWidget *combo_box, GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gint ival; + GValue val = { 0, }; + GParamSpec *pspec; + GtkTreeModel *tree_model; + GtkTreeIter iter; + + if (priv->loading) + return; + + tree_model = gtk_combo_box_get_model (GTK_COMBO_BOX (combo_box)); + gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combo_box), &iter); + gtk_tree_model_get (tree_model, &iter, 1, &ival, -1); + + pspec = glade_property_def_get_pspec (priv->property_def); + + g_value_init (&val, pspec->value_type); + g_value_set_enum (&val, ival); + + glade_editor_property_commit_no_callback (eprop, &val); + g_value_unset (&val); +} + +static GtkWidget * +glade_eprop_enum_create_input (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropEnum *eprop_enum = GLADE_EPROP_ENUM (eprop); + GladePropertyDef *property_def; + GParamSpec *pspec; + GEnumClass *eclass; + GtkListStore *list_store; + GtkTreeIter iter; + GtkCellRenderer *cell_renderer; + guint i; + + property_def = priv->property_def; + pspec = glade_property_def_get_pspec (property_def); + eclass = g_type_class_ref (pspec->value_type); + + list_store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT); + + gtk_tree_model_get_iter_first (GTK_TREE_MODEL (list_store), &iter); + + for (i = 0; i < eclass->n_values; i++) + { + const gchar *value_name; + + if (glade_displayable_value_is_disabled (pspec->value_type, + eclass->values[i].value_nick)) + continue; + + value_name = glade_get_displayable_value (pspec->value_type, + eclass->values[i].value_nick); + if (value_name == NULL) + value_name = eclass->values[i].value_nick; + + gtk_list_store_append (list_store, &iter); + gtk_list_store_set (list_store, &iter, 0, value_name, 1, + eclass->values[i].value, -1); + } + + eprop_enum->combo_box = gtk_combo_box_new_with_model (GTK_TREE_MODEL (list_store)); + gtk_widget_set_halign (eprop_enum->combo_box, GTK_ALIGN_FILL); + gtk_widget_set_valign (eprop_enum->combo_box, GTK_ALIGN_CENTER); + gtk_widget_set_hexpand (eprop_enum->combo_box, TRUE); + + cell_renderer = gtk_cell_renderer_text_new (); + g_object_set (cell_renderer, + "wrap-mode", PANGO_WRAP_WORD, + "wrap-width", 1, + "width-chars", 8, + NULL); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (eprop_enum->combo_box), + cell_renderer, TRUE); + gtk_cell_layout_add_attribute (GTK_CELL_LAYOUT (eprop_enum->combo_box), + cell_renderer, "text", 0); + + g_signal_connect (G_OBJECT (eprop_enum->combo_box), "changed", + G_CALLBACK (glade_eprop_enum_changed), eprop); + + glade_util_remove_scroll_events (eprop_enum->combo_box); + gtk_widget_show_all (eprop_enum->combo_box); + + g_type_class_unref (eclass); + + return eprop_enum->combo_box; +} + +/******************************************************************************* + GladeEditorPropertyFlagsClass + *******************************************************************************/ +struct _GladeEPropFlags +{ + GladeEditorProperty parent_instance; + + GtkTreeModel *model; + GtkWidget *entry; +}; + +GLADE_MAKE_EPROP (GladeEPropFlags, glade_eprop_flags, GLADE, EPROP_FLAGS) + +static void +glade_eprop_flags_finalize (GObject *object) +{ + GladeEPropFlags *eprop_flags = GLADE_EPROP_FLAGS (object); + + g_object_unref (G_OBJECT (eprop_flags->model)); + + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_flags_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropFlags *eprop_flags = GLADE_EPROP_FLAGS (eprop); + GFlagsClass *klass; + GParamSpec *pspec; + guint flag_num, value; + GString *string = g_string_new (NULL); + + /* Chain up first */ + editor_property_class->load (eprop, property); + + gtk_list_store_clear (GTK_LIST_STORE (eprop_flags->model)); + + if (property) + { + /* Populate the model with the flags. */ + klass = g_type_class_ref (G_VALUE_TYPE (glade_property_inline_value (property))); + value = g_value_get_flags (glade_property_inline_value (property)); + pspec = glade_property_def_get_pspec (priv->property_def); + + /* Step through each of the flags in the class. */ + for (flag_num = 0; flag_num < klass->n_values; flag_num++) + { + GtkTreeIter iter; + guint mask; + gboolean setting; + const gchar *value_name; + + if (glade_displayable_value_is_disabled (pspec->value_type, + klass->values[flag_num].value_nick)) + continue; + + mask = klass->values[flag_num].value; + setting = ((value & mask) == mask) ? TRUE : FALSE; + + value_name = glade_get_displayable_value + (pspec->value_type, klass->values[flag_num].value_nick); + + if (value_name == NULL) + value_name = klass->values[flag_num].value_name; + + /* Setup string for property label */ + if (setting) + { + if (string->len > 0) + g_string_append (string, " | "); + g_string_append (string, value_name); + } + + /* Add a row to represent the flag. */ + gtk_list_store_append (GTK_LIST_STORE (eprop_flags->model), &iter); + gtk_list_store_set (GTK_LIST_STORE (eprop_flags->model), &iter, + FLAGS_COLUMN_SETTING, setting, + FLAGS_COLUMN_SYMBOL, value_name, + FLAGS_COLUMN_VALUE, mask, -1); + + } + g_type_class_unref (klass); + } + + gtk_entry_set_text (GTK_ENTRY (eprop_flags->entry), string->str); + + g_string_free (string, TRUE); +} + + +static void +flag_toggled_direct (GtkCellRendererToggle *cell, + gchar *path_string, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GtkTreeIter iter; + guint new_value = 0; + gboolean selected; + GValue *gvalue; + gboolean valid; + + GladeEPropFlags *eprop_flags = GLADE_EPROP_FLAGS (eprop); + + if (!priv->property) + return; + + gvalue = glade_property_inline_value (priv->property); + + gtk_tree_model_get_iter_from_string (eprop_flags->model, &iter, path_string); + + gtk_tree_model_get (eprop_flags->model, &iter, + FLAGS_COLUMN_SETTING, &selected, -1); + + selected = selected ? FALSE : TRUE; + + gtk_list_store_set (GTK_LIST_STORE (eprop_flags->model), &iter, + FLAGS_COLUMN_SETTING, selected, -1); + + valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (eprop_flags->model), &iter); + + /* Step through each of the flags in the class, checking if + the corresponding toggle in the dialog is selected, If it + is, OR the flags' mask with the new value. */ + while (valid) + { + gboolean setting; + guint value; + + gtk_tree_model_get (GTK_TREE_MODEL (eprop_flags->model), &iter, + FLAGS_COLUMN_SETTING, &setting, + FLAGS_COLUMN_VALUE, &value, -1); + + if (setting) new_value |= value; + + valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (eprop_flags->model), &iter); + } + + /* If the new_value is different from the old value, we need + to update the property. */ + if (new_value != g_value_get_flags (gvalue)) + { + GValue val = { 0, }; + + g_value_init (&val, G_VALUE_TYPE (gvalue)); + g_value_set_flags (&val, new_value); + + glade_editor_property_commit_no_callback (eprop, &val); + g_value_unset (&val); + } +} + +static GtkWidget * +glade_eprop_flags_create_treeview (GladeEditorProperty *eprop) +{ + GtkWidget *scrolled_window; + GtkWidget *tree_view; + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GladeEPropFlags *eprop_flags = GLADE_EPROP_FLAGS (eprop); + + scrolled_window = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window), + GTK_SHADOW_IN); + gtk_widget_show (scrolled_window); + + + + tree_view = + gtk_tree_view_new_with_model (GTK_TREE_MODEL (eprop_flags->model)); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (tree_view), FALSE); + gtk_widget_show (tree_view); + gtk_container_add (GTK_CONTAINER (scrolled_window), tree_view); + + column = gtk_tree_view_column_new (); + + renderer = gtk_cell_renderer_toggle_new (); + gtk_tree_view_column_pack_start (column, renderer, FALSE); + gtk_tree_view_column_set_attributes (column, renderer, + "active", FLAGS_COLUMN_SETTING, NULL); + + g_signal_connect (renderer, "toggled", + G_CALLBACK (flag_toggled_direct), eprop); + + + renderer = gtk_cell_renderer_text_new (); + gtk_tree_view_column_pack_start (column, renderer, TRUE); + gtk_tree_view_column_set_attributes (column, renderer, + "text", FLAGS_COLUMN_SYMBOL, NULL); + + gtk_tree_view_append_column (GTK_TREE_VIEW (tree_view), column); + + + + return scrolled_window; +} + +static void +glade_eprop_flags_show_dialog (GladeEditorProperty *eprop) +{ + GtkWidget *window = gtk_widget_get_toplevel (GTK_WIDGET (eprop)); + GtkWidget *dialog; + GtkWidget *view; + GtkWidget *label; + GtkWidget *vbox; + GtkWidget *content_area; + + dialog = gtk_dialog_new_with_buttons (_("Select Fields"), + GTK_WINDOW (window), + GTK_DIALOG_MODAL, + _("_Close"), GTK_RESPONSE_CLOSE, + NULL); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 300, 400); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CLOSE); + + _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + view = glade_eprop_flags_create_treeview (eprop); + + label = gtk_label_new_with_mnemonic (_("_Select individual fields:")); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), + gtk_bin_get_child (GTK_BIN (view))); + + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (vbox), view, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0); + + gtk_widget_show (label); + gtk_widget_show (view); + gtk_widget_show (vbox); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); +} + + +static GtkWidget * +glade_eprop_flags_create_input (GladeEditorProperty *eprop) +{ + GladeEPropFlags *eprop_flags = GLADE_EPROP_FLAGS (eprop); + + if (!eprop_flags->model) + eprop_flags->model = GTK_TREE_MODEL (gtk_list_store_new (3, G_TYPE_BOOLEAN, + G_TYPE_STRING, + G_TYPE_UINT)); + + eprop_flags->entry = gtk_entry_new (); + gtk_widget_set_hexpand (eprop_flags->entry, TRUE); + + gtk_editable_set_editable (GTK_EDITABLE (eprop_flags->entry), FALSE); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (eprop_flags->entry), + GTK_ENTRY_ICON_SECONDARY, + "document-edit-symbolic"); + + g_signal_connect_swapped (eprop_flags->entry, "icon-release", + G_CALLBACK (glade_eprop_flags_show_dialog), + eprop); + + return eprop_flags->entry; +} + +/******************************************************************************* + GladeEditorPropertyColorClass + *******************************************************************************/ +struct _GladeEPropColor +{ + GladeEditorProperty parent_instance; + + GtkWidget *cbutton; + GtkWidget *entry; +}; + +GLADE_MAKE_EPROP (GladeEPropColor, glade_eprop_color, GLADE, EPROP_COLOR) + +static void +glade_eprop_color_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_color_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropColor *eprop_color = GLADE_EPROP_COLOR (eprop); + GParamSpec *pspec; + GdkColor *color; + PangoColor *pango_color; + GdkRGBA *rgba; + gchar *text; + + /* Chain up first */ + editor_property_class->load (eprop, property); + + pspec = glade_property_def_get_pspec (priv->property_def); + + if (property) + { + if ((text = glade_property_make_string (property)) != NULL) + { + gtk_entry_set_text (GTK_ENTRY (eprop_color->entry), text); + g_free (text); + } + else + gtk_entry_set_text (GTK_ENTRY (eprop_color->entry), ""); + + if (pspec->value_type == GDK_TYPE_COLOR) + { + if ((color = g_value_get_boxed (glade_property_inline_value (property))) != NULL) + { + GdkRGBA copy; + + copy.red = color->red / 65535.0; + copy.green = color->green / 65535.0; + copy.blue = color->blue / 65535.0; + copy.alpha = 1.0; + + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (eprop_color->cbutton), ©); + } + else + { + GdkRGBA black = { 0, }; + + /* Manually fill it with black for an NULL value. + */ + if (gdk_rgba_parse (&black, "Black")) + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (eprop_color->cbutton), &black); + } + } + else if (pspec->value_type == PANGO_TYPE_COLOR) + { + if ((pango_color = g_value_get_boxed (glade_property_inline_value (property))) != NULL) + { + GdkRGBA copy; + + copy.red = pango_color->red / 65535.0; + copy.green = pango_color->green / 65535.0; + copy.blue = pango_color->blue / 65535.0; + copy.alpha = 1.0; + + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (eprop_color->cbutton), ©); + } + else + { + GdkRGBA black = { 0, }; + + /* Manually fill it with black for an NULL value. + */ + if (gdk_rgba_parse (&black, "Black")) + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (eprop_color->cbutton), &black); + } + } + else if (pspec->value_type == GDK_TYPE_RGBA) + { + if ((rgba = g_value_get_boxed (glade_property_inline_value (property))) != NULL) + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (eprop_color->cbutton), rgba); + else + { + GdkRGBA black = { 0, }; + + /* Manually fill it with black for an NULL value. + */ + if (gdk_rgba_parse (&black, "Black")) + gtk_color_chooser_set_rgba (GTK_COLOR_CHOOSER (eprop_color->cbutton), &black); + } + } + } +} + +static void +glade_eprop_color_changed (GtkWidget *button, GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GdkRGBA rgba = { 0, }; + GValue value = { 0, }; + GParamSpec *pspec; + + if (priv->loading) + return; + + pspec = glade_property_def_get_pspec (priv->property_def); + g_value_init (&value, pspec->value_type); + + gtk_color_chooser_get_rgba (GTK_COLOR_CHOOSER (button), &rgba); + + if (pspec->value_type == GDK_TYPE_COLOR) + { + GdkColor color = { 0, }; + + color.red = (gint16) (rgba.red * 65535); + color.green = (gint16) (rgba.green * 65535); + color.blue = (gint16) (rgba.blue * 65535); + + g_value_set_boxed (&value, &color); + } + else if (pspec->value_type == PANGO_TYPE_COLOR) + { + PangoColor color = { 0, }; + + color.red = (gint16) (rgba.red * 65535); + color.green = (gint16) (rgba.green * 65535); + color.blue = (gint16) (rgba.blue * 65535); + + g_value_set_boxed (&value, &color); + } + else if (pspec->value_type == GDK_TYPE_RGBA) + g_value_set_boxed (&value, &rgba); + + glade_editor_property_commit (eprop, &value); + g_value_unset (&value); +} + +static GtkWidget * +glade_eprop_color_create_input (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropColor *eprop_color = GLADE_EPROP_COLOR (eprop); + GtkWidget *hbox; + GParamSpec *pspec; + + pspec = glade_property_def_get_pspec (priv->property_def); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_widget_set_halign (hbox, GTK_ALIGN_START); + gtk_widget_set_valign (hbox, GTK_ALIGN_CENTER); + + eprop_color->entry = gtk_entry_new (); + gtk_widget_set_hexpand (eprop_color->entry, TRUE); + gtk_editable_set_editable (GTK_EDITABLE (eprop_color->entry), FALSE); + gtk_widget_show (eprop_color->entry); + gtk_box_pack_start (GTK_BOX (hbox), eprop_color->entry, TRUE, TRUE, 0); + + eprop_color->cbutton = gtk_color_button_new (); + gtk_widget_show (eprop_color->cbutton); + gtk_box_pack_start (GTK_BOX (hbox), eprop_color->cbutton, FALSE, FALSE, 0); + + if (pspec->value_type == GDK_TYPE_RGBA) + gtk_color_chooser_set_use_alpha (GTK_COLOR_CHOOSER (eprop_color->cbutton), TRUE); + else + gtk_color_chooser_set_use_alpha (GTK_COLOR_CHOOSER (eprop_color->cbutton), FALSE); + + g_signal_connect (G_OBJECT (eprop_color->cbutton), "color-set", + G_CALLBACK (glade_eprop_color_changed), eprop); + + return hbox; +} + +/******************************************************************************* + GladeEditorPropertyNamedIconClass + *******************************************************************************/ +struct _GladeEPropNamedIcon +{ + GladeEditorProperty parent_instance; + + GtkWidget *entry; + gchar *current_context; +}; + +GLADE_MAKE_EPROP (GladeEPropNamedIcon, glade_eprop_named_icon, GLADE, EPROP_NAMED_ICON) + +static void +glade_eprop_named_icon_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_named_icon_load (GladeEditorProperty *eprop, + GladeProperty *property) +{ + GladeEPropNamedIcon *eprop_named_icon = GLADE_EPROP_NAMED_ICON (eprop); + GtkEntry *entry; + gchar *text; + + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property == NULL) + return; + + entry = GTK_ENTRY (eprop_named_icon->entry); + text = glade_property_make_string (property); + + gtk_entry_set_text (entry, text ? text : ""); + + g_free (text); +} + +static void +glade_eprop_named_icon_changed_common (GladeEditorProperty *eprop, + const gchar *text, + gboolean use_command) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GValue *val; + gchar *prop_text; + + val = g_new0 (GValue, 1); + + g_value_init (val, G_TYPE_STRING); + + glade_property_get (priv->property, &prop_text); + + /* Here we try not to modify the project state by not + * modifying a null value for an unchanged property. + */ + if (prop_text == NULL && text && *text == '\0') + g_value_set_string (val, NULL); + else if (text == NULL && prop_text && *prop_text == '\0') + g_value_set_string (val, ""); + else + g_value_set_string (val, text); + + glade_editor_property_commit (eprop, val); + g_value_unset (val); + g_free (val); +} + +static void +glade_eprop_named_icon_changed (GtkWidget *entry, GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gchar *text; + + if (priv->loading) + return; + + text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1); + glade_eprop_named_icon_changed_common (eprop, text, priv->use_command); + + g_free (text); +} + +static gboolean +glade_eprop_named_icon_focus_out (GtkWidget *entry, + GdkEventFocus *event, + GladeEditorProperty *eprop) +{ + glade_eprop_named_icon_changed (entry, eprop); + return FALSE; +} + +static void +glade_eprop_named_icon_activate (GtkEntry *entry, GladeEPropNamedIcon *eprop) +{ + glade_eprop_named_icon_changed (GTK_WIDGET (entry), + GLADE_EDITOR_PROPERTY (eprop)); +} + +static void +chooser_response (GladeNamedIconChooserDialog *dialog, + gint response_id, + GladeEPropNamedIcon *eprop) +{ + gchar *icon_name; + + switch (response_id) + { + + case GTK_RESPONSE_OK: + + g_free (eprop->current_context); + eprop->current_context = + glade_named_icon_chooser_dialog_get_context (dialog); + icon_name = glade_named_icon_chooser_dialog_get_icon_name (dialog); + + gtk_entry_set_text (GTK_ENTRY (eprop->entry), icon_name); + gtk_widget_destroy (GTK_WIDGET (dialog)); + + g_free (icon_name); + + glade_eprop_named_icon_changed (eprop->entry, + GLADE_EDITOR_PROPERTY (eprop)); + + break; + + case GTK_RESPONSE_CANCEL: + + gtk_widget_destroy (GTK_WIDGET (dialog)); + break; + + case GTK_RESPONSE_HELP: + + break; + + case GTK_RESPONSE_DELETE_EVENT: + gtk_widget_destroy (GTK_WIDGET (dialog)); + } +} + +static void +glade_eprop_named_icon_show_chooser_dialog (GladeEditorProperty *eprop) +{ + GtkWidget *dialog; + + dialog = glade_named_icon_chooser_dialog_new (_("Select Named Icon"), + GTK_WINDOW + (gtk_widget_get_toplevel + (GTK_WIDGET (eprop))), + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, + NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + glade_named_icon_chooser_dialog_set_context (GLADE_NAMED_ICON_CHOOSER_DIALOG + (dialog), + GLADE_EPROP_NAMED_ICON (eprop)-> + current_context); + + glade_named_icon_chooser_dialog_set_icon_name (GLADE_NAMED_ICON_CHOOSER_DIALOG + (dialog), + gtk_entry_get_text (GTK_ENTRY + (GLADE_EPROP_NAMED_ICON + (eprop)-> + entry))); + + + g_signal_connect (dialog, "response", G_CALLBACK (chooser_response), eprop); + + gtk_widget_show (dialog); + +} + +static GtkWidget * +glade_eprop_named_icon_create_input (GladeEditorProperty *eprop) +{ + GladeEPropNamedIcon *eprop_named_icon = GLADE_EPROP_NAMED_ICON (eprop); + + eprop_named_icon->entry = gtk_entry_new (); + gtk_widget_set_hexpand (eprop_named_icon->entry, TRUE); + gtk_widget_set_valign (eprop_named_icon->entry, GTK_ALIGN_CENTER); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (eprop_named_icon->entry), + GTK_ENTRY_ICON_SECONDARY, + "document-edit-symbolic"); + + eprop_named_icon->current_context = NULL; + + g_signal_connect (G_OBJECT (eprop_named_icon->entry), "activate", + G_CALLBACK (glade_eprop_named_icon_activate), eprop); + + g_signal_connect (G_OBJECT (eprop_named_icon->entry), "focus-out-event", + G_CALLBACK (glade_eprop_named_icon_focus_out), eprop); + + g_signal_connect_swapped (eprop_named_icon->entry, "icon-release", + G_CALLBACK (glade_eprop_named_icon_show_chooser_dialog), + eprop); + + return eprop_named_icon->entry; +} + + + +/******************************************************************************* + GladeEditorPropertyTextClass + *******************************************************************************/ +struct _GladeEPropText +{ + GladeEditorProperty parent_instance; + + GtkWidget *text_entry; + GtkTreeModel *store; +}; + +GLADE_MAKE_EPROP (GladeEPropText, glade_eprop_text, GLADE, EPROP_TEXT) + +static void +glade_eprop_text_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static gchar * +text_buffer_get_text (GtkTextBuffer *buffer) +{ + GtkTextIter start, end; + gchar *retval; + + gtk_text_buffer_get_bounds (buffer, &start, &end); + retval = gtk_text_buffer_get_text (buffer, &start, &end, FALSE); + + if (retval && *retval == '\0') + { + g_free (retval); + return NULL; + } + + return retval; +} + +static void +glade_eprop_text_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropText *eprop_text = GLADE_EPROP_TEXT (eprop); + GParamSpec *pspec; + + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property == NULL) + return; + + pspec = glade_property_def_get_pspec (priv->property_def); + + if (GTK_IS_COMBO_BOX (eprop_text->text_entry)) + { + if (gtk_combo_box_get_has_entry (GTK_COMBO_BOX (eprop_text->text_entry))) + { + GtkWidget *entry = gtk_bin_get_child (GTK_BIN (eprop_text->text_entry)); + gchar *text = glade_property_make_string (property); + + gtk_entry_set_text (GTK_ENTRY (entry), text ? text : ""); + g_free (text); + } + else + { + gchar *text = glade_property_make_string (property); + gint value = text ? + glade_utils_enum_value_from_string (GLADE_TYPE_STOCK, text) : 0; + + /* Set active iter... */ + gtk_combo_box_set_active (GTK_COMBO_BOX (eprop_text->text_entry), + value); + g_free (text); + } + } + else if (GTK_IS_ENTRY (eprop_text->text_entry)) + { + GtkEntry *entry = GTK_ENTRY (eprop_text->text_entry); + gchar *text = glade_property_make_string (property); + + gtk_entry_set_text (entry, text ? text : ""); + g_free (text); + } + else if (GTK_IS_TEXT_VIEW (eprop_text->text_entry)) + { + GtkTextBuffer *buffer; + GType value_array_type; + + /* Deprecated GValueArray */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + value_array_type = g_value_array_get_type (); + G_GNUC_END_IGNORE_DEPRECATIONS; + + buffer = + gtk_text_view_get_buffer (GTK_TEXT_VIEW (eprop_text->text_entry)); + + if (pspec->value_type == G_TYPE_STRV || + pspec->value_type == value_array_type) + { + GladePropertyDef *pclass = glade_property_get_def (property); + gchar *text = glade_widget_adaptor_string_from_value + (glade_property_def_get_adaptor (pclass), + pclass, glade_property_inline_value (property)); + gchar *old_text = text_buffer_get_text (buffer); + + /* Only update it if necessary, see notes bellow */ + if (g_strcmp0 (text, old_text)) + gtk_text_buffer_set_text (buffer, text ? text : "", -1); + + g_free (text); + } + else + { + gchar *text = glade_property_make_string (property); + gchar *old_text = text_buffer_get_text (buffer); + + /* NOTE: GtkTextBuffer does not like to be updated from a "changed" + * signal callback. It prints a iterator warning and moves the cursor + * to the end. + */ + if (g_strcmp0 (text, old_text)) + gtk_text_buffer_set_text (buffer, text ? text : "", -1); + + g_free (old_text); + g_free (text); + } + } + else + { + g_warning ("BUG! Invalid Text Widget type."); + } +} + +static void +glade_eprop_text_changed_common (GladeEditorProperty *eprop, + const gchar *text, + gboolean use_command) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GValue *val; + GParamSpec *pspec; + gchar *prop_text; + GType value_array_type; + + /* Deprecated GValueArray */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + value_array_type = g_value_array_get_type (); + G_GNUC_END_IGNORE_DEPRECATIONS; + + pspec = glade_property_def_get_pspec (priv->property_def); + + if (pspec->value_type == G_TYPE_STRV || + pspec->value_type == value_array_type || + pspec->value_type == GDK_TYPE_PIXBUF || + pspec->value_type == G_TYPE_FILE || + pspec->value_type == G_TYPE_VARIANT) + { + GladeWidget *gwidget = glade_property_get_widget (priv->property); + + val = glade_property_def_make_gvalue_from_string (priv->property_def, + text, + glade_widget_get_project (gwidget)); + } + else + { + val = g_new0 (GValue, 1); + + g_value_init (val, G_TYPE_STRING); + + glade_property_get (priv->property, &prop_text); + + /* Here we try not to modify the project state by not + * modifying a null value for an unchanged property. + */ + if (prop_text == NULL && text && *text == '\0') + g_value_set_string (val, NULL); + else if (text == NULL && prop_text && *prop_text == '\0') + g_value_set_string (val, ""); + else + g_value_set_string (val, text); + } + + glade_editor_property_commit_no_callback (eprop, val); + g_value_unset (val); + g_free (val); +} + +static void +glade_eprop_text_changed (GtkWidget *entry, GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gchar *text; + + if (priv->loading) + return; + + text = gtk_editable_get_chars (GTK_EDITABLE (entry), 0, -1); + glade_eprop_text_changed_common (eprop, text, priv->use_command); + + g_free (text); +} + +static void +glade_eprop_text_buffer_changed (GtkTextBuffer *buffer, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gchar *text; + + if (priv->loading) + return; + + text = text_buffer_get_text (buffer); + glade_eprop_text_changed_common (eprop, text, priv->use_command); + g_free (text); +} + +/** + * glade_editor_property_show_i18n_dialog: + * @parent: The parent widget for the dialog. + * @text: A read/write pointer to the text property + * @context: A read/write pointer to the translation context + * @comment: A read/write pointer to the translator comment + * @translatable: A read/write pointer to the translatable setting] + * + * Runs a dialog and updates the provided values. + * + * Returns: %TRUE if OK was selected. + */ +gboolean +glade_editor_property_show_i18n_dialog (GtkWidget *parent, + gchar **text, + gchar **context, + gchar **comment, + gboolean *translatable) +{ + GtkWidget *dialog; + GtkWidget *vbox, *hbox; + GtkWidget *label; + GtkWidget *sw; + GtkWidget *alignment; + GtkWidget *text_view, *comment_view, *context_view; + GtkTextBuffer *text_buffer, *comment_buffer, *context_buffer = NULL; + GtkWidget *translatable_button; + GtkWidget *content_area; + gint res; + + g_return_val_if_fail (text && context && comment && translatable, FALSE); + + dialog = gtk_dialog_new_with_buttons (_("Edit Text"), + parent ? + GTK_WINDOW (gtk_widget_get_toplevel + (parent)) : NULL, + GTK_DIALOG_MODAL, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + gtk_widget_show (vbox); + + gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0); + + /* Text */ + label = gtk_label_new_with_mnemonic (_("_Text:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_set_size_request (sw, 400, 200); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + text_view = gtk_text_view_new (); + gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (text_view), GTK_SCROLL_MINIMUM); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (text_view), GTK_WRAP_WORD); + gtk_widget_show (text_view); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), text_view); + + gtk_container_add (GTK_CONTAINER (sw), text_view); + + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (text_view)); + + if (*text) + { + gtk_text_buffer_set_text (text_buffer, *text, -1); + } + + /* Translatable and context prefix. */ + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (hbox); + + gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + + /* Translatable */ + translatable_button = gtk_check_button_new_with_mnemonic (_("T_ranslatable")); + gtk_widget_show (translatable_button); + gtk_box_pack_start (GTK_BOX (hbox), translatable_button, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (translatable_button), + *translatable); + gtk_widget_set_tooltip_text (translatable_button, + _("Whether this property is translatable")); + + + /* Context. */ + alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 0, 0, 0); + gtk_widget_show (alignment); + + label = gtk_label_new_with_mnemonic (_("Conte_xt for translation:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_container_add (GTK_CONTAINER (alignment), label); + gtk_box_pack_start (GTK_BOX (vbox), alignment, FALSE, FALSE, 0); + gtk_widget_set_tooltip_text (alignment, + _("For short and ambiguous strings: type a word here to differentiate " + "the meaning of this string from the meaning of other occurrences of " + "the same string")); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + context_view = gtk_text_view_new (); + gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (context_view), GTK_SCROLL_MINIMUM); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (context_view), GTK_WRAP_WORD); + gtk_widget_show (context_view); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), context_view); + + gtk_container_add (GTK_CONTAINER (sw), context_view); + + context_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (context_view)); + + if (*context) + { + gtk_text_buffer_set_text (context_buffer, *context, -1); + } + + /* Comments. */ + alignment = gtk_alignment_new (0.5, 0.5, 1, 1); + gtk_alignment_set_padding (GTK_ALIGNMENT (alignment), 12, 0, 0, 0); + gtk_widget_show (alignment); + + label = gtk_label_new_with_mnemonic (_("Co_mments for translators:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_container_add (GTK_CONTAINER (alignment), label); + gtk_box_pack_start (GTK_BOX (vbox), alignment, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + comment_view = gtk_text_view_new (); + gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (comment_view), GTK_SCROLL_MINIMUM); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (comment_view), GTK_WRAP_WORD); + gtk_widget_show (comment_view); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), comment_view); + + gtk_container_add (GTK_CONTAINER (sw), comment_view); + + comment_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (comment_view)); + + if (*comment) + { + gtk_text_buffer_set_text (comment_buffer, *comment, -1); + } + + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_OK) + { + g_free ((gpointer) * text); + g_free ((gpointer) * context); + g_free ((gpointer) * comment); + + /* Get the new values for translatable */ + *translatable = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON + (translatable_button)); + + /* Comment, text and context */ + *comment = text_buffer_get_text (comment_buffer); + *text = text_buffer_get_text (text_buffer); + *context = text_buffer_get_text (context_buffer); + + gtk_widget_destroy (dialog); + return TRUE; + } + + gtk_widget_destroy (dialog); + return FALSE; +} + +static void +glade_eprop_text_show_i18n_dialog (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gchar *text = glade_property_make_string (priv->property); + gchar *context = g_strdup (glade_property_i18n_get_context (priv->property)); + gchar *comment = g_strdup (glade_property_i18n_get_comment (priv->property)); + gboolean translatable = glade_property_i18n_get_translatable (priv->property); + + if (glade_editor_property_show_i18n_dialog + (GTK_WIDGET (eprop), &text, &context, &comment, &translatable)) + { + glade_command_set_i18n (priv->property, translatable, context, comment); + glade_eprop_text_changed_common (eprop, text, priv->use_command); + + glade_editor_property_load (eprop, priv->property); + } + + g_free (text); + g_free (context); + g_free (comment); +} + +gboolean +glade_editor_property_show_resource_dialog (GladeProject *project, + GtkWidget *parent, + gchar **filename) +{ + GFile *resource_folder; + GtkWidget *dialog; + gchar *folder; + + g_return_val_if_fail (filename != NULL, FALSE); + + *filename = NULL; + + dialog = + gtk_file_chooser_dialog_new (_ + ("Select a file from the project resource directory"), + parent ? + GTK_WINDOW (gtk_widget_get_toplevel (parent)) + : NULL, GTK_FILE_CHOOSER_ACTION_OPEN, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_Open"), GTK_RESPONSE_OK, NULL); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); + + folder = glade_project_resource_fullpath (project, ""); + gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (dialog), folder); + resource_folder = g_file_new_for_path (folder); + g_free (folder); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_OK) + { + GFile *file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (dialog)); + *filename = _glade_util_file_get_relative_path (resource_folder, file); + g_object_unref (file); + } + + gtk_widget_destroy (dialog); + g_object_unref (resource_folder); + + return *filename != NULL; +} + +static void +glade_eprop_text_show_resource_dialog (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeWidget *widget = glade_property_get_widget (priv->property); + GladeProject *project = glade_widget_get_project (widget); + gchar *text = NULL; + + if (glade_editor_property_show_resource_dialog (project, GTK_WIDGET (eprop), &text)) + { + GParamSpec *pspec = glade_property_def_get_pspec (priv->property_def); + + if (G_PARAM_SPEC_VALUE_TYPE (pspec) == G_TYPE_FILE) + { + gchar *path = text; + text = g_strconcat ("file://", path, NULL); + g_free (path); + } + + glade_eprop_text_changed_common (eprop, text, priv->use_command); + + glade_editor_property_load (eprop, priv->property); + + g_free (text); + } +} + +enum +{ + COMBO_COLUMN_TEXT = 0, + COMBO_COLUMN_PIXBUF, + COMBO_LAST_COLUMN +}; + +static GtkListStore * +glade_eprop_text_create_store (GType enum_type) +{ + GtkListStore *store; + GtkTreeIter iter; + GEnumClass *eclass; + guint i; + + eclass = g_type_class_ref (enum_type); + + store = gtk_list_store_new (COMBO_LAST_COLUMN, G_TYPE_STRING, G_TYPE_STRING); + + for (i = 0; i < eclass->n_values; i++) + { + const gchar *displayable = + glade_get_displayable_value (enum_type, eclass->values[i].value_nick); + if (!displayable) + displayable = eclass->values[i].value_nick; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + COMBO_COLUMN_TEXT, displayable, + COMBO_COLUMN_PIXBUF, eclass->values[i].value_nick, + -1); + } + + g_type_class_unref (eclass); + + return store; +} + +static void +eprop_text_stock_changed (GtkComboBox *combo, GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropText *eprop_text = GLADE_EPROP_TEXT (eprop); + GtkTreeIter iter; + gchar *text = NULL; + const gchar *str; + + if (priv->loading) + return; + + if (gtk_combo_box_get_active_iter (combo, &iter)) + { + gtk_tree_model_get (GTK_TREE_MODEL (eprop_text->store), &iter, + COMBO_COLUMN_PIXBUF, &text, -1); + glade_eprop_text_changed_common (eprop, text, priv->use_command); + g_free (text); + } + else if (gtk_combo_box_get_has_entry (combo)) + { + str = + gtk_entry_get_text (GTK_ENTRY (gtk_bin_get_child (GTK_BIN (combo)))); + glade_eprop_text_changed_common (eprop, str, priv->use_command); + } +} + +static gint +get_text_view_height (void) +{ + static gint height = -1; + + if (height < 0) + { + GtkWidget *label = gtk_label_new (NULL); + PangoLayout *layout = + gtk_widget_create_pango_layout (label, + "The quick\n" + "brown fox\n" + "jumped over\n" + "the lazy dog"); + + pango_layout_get_pixel_size (layout, NULL, &height); + + g_object_unref (layout); + g_object_ref_sink (label); + g_object_unref (label); + } + + return height; +} + +static GtkWidget * +glade_eprop_text_create_input (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropText *eprop_text = GLADE_EPROP_TEXT (eprop); + GladePropertyDef *property_def; + GParamSpec *pspec; + GtkWidget *hbox; + GType value_array_type; + + /* Deprecated GValueArray */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + value_array_type = g_value_array_get_type (); + G_GNUC_END_IGNORE_DEPRECATIONS; + + property_def = priv->property_def; + pspec = glade_property_def_get_pspec (property_def); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + + if (glade_property_def_stock (property_def) || + glade_property_def_stock_icon (property_def)) + { + GtkCellRenderer *renderer; + GtkWidget *child; + GtkWidget *combo = gtk_combo_box_new_with_entry (); + + gtk_widget_set_halign (hbox, GTK_ALIGN_START); + gtk_widget_set_valign (hbox, GTK_ALIGN_CENTER); + gtk_widget_set_hexpand (combo, TRUE); + glade_util_remove_scroll_events (combo); + + eprop_text->store = (GtkTreeModel *) + glade_eprop_text_create_store (glade_property_def_stock (property_def) ? + GLADE_TYPE_STOCK : GLADE_TYPE_STOCK_IMAGE); + + gtk_combo_box_set_model (GTK_COMBO_BOX (combo), + GTK_TREE_MODEL (eprop_text->store)); + + /* let the comboboxentry prepend its intrusive cell first... */ + gtk_combo_box_set_entry_text_column (GTK_COMBO_BOX (combo), + COMBO_COLUMN_TEXT); + + renderer = gtk_cell_renderer_pixbuf_new (); + gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combo), renderer, FALSE); + gtk_cell_layout_reorder (GTK_CELL_LAYOUT (combo), renderer, 0); + gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combo), renderer, + "stock-id", COMBO_COLUMN_PIXBUF, NULL); + + /* Dont allow custom items where an actual GTK+ stock item is expected + * (i.e. real items come with labels) */ + child = gtk_bin_get_child (GTK_BIN (combo)); + if (glade_property_def_stock (property_def)) + gtk_editable_set_editable (GTK_EDITABLE (child), FALSE); + else + gtk_editable_set_editable (GTK_EDITABLE (child), TRUE); + + gtk_widget_show (combo); + gtk_box_pack_start (GTK_BOX (hbox), combo, FALSE, FALSE, 0); + g_signal_connect (G_OBJECT (combo), "changed", + G_CALLBACK (eprop_text_stock_changed), eprop); + + + eprop_text->text_entry = combo; + } + else if (glade_property_def_multiline (property_def) || + pspec->value_type == G_TYPE_STRV || + pspec->value_type == value_array_type) + { + GtkWidget *swindow; + GtkTextBuffer *buffer; + gint min_height = get_text_view_height (); + + swindow = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (swindow), min_height); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (swindow), + GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (swindow), + GTK_SHADOW_IN); + glade_util_remove_scroll_events (swindow); + + eprop_text->text_entry = gtk_text_view_new (); + gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (eprop_text->text_entry), GTK_SCROLL_MINIMUM); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (eprop_text->text_entry), GTK_WRAP_WORD); + buffer = + gtk_text_view_get_buffer (GTK_TEXT_VIEW (eprop_text->text_entry)); + + gtk_container_add (GTK_CONTAINER (swindow), eprop_text->text_entry); + gtk_box_pack_start (GTK_BOX (hbox), GTK_WIDGET (swindow), TRUE, TRUE, 0); + + gtk_widget_show_all (swindow); + + gtk_widget_set_hexpand (swindow, TRUE); + + g_signal_connect (G_OBJECT (buffer), "changed", + G_CALLBACK (glade_eprop_text_buffer_changed), eprop); + + } + else + { + eprop_text->text_entry = gtk_entry_new (); + gtk_widget_set_hexpand (eprop_text->text_entry, TRUE); + gtk_widget_show (eprop_text->text_entry); + + gtk_box_pack_start (GTK_BOX (hbox), eprop_text->text_entry, TRUE, TRUE, 0); + + g_signal_connect (G_OBJECT (eprop_text->text_entry), "changed", + G_CALLBACK (glade_eprop_text_changed), eprop); + + if (pspec->value_type == GDK_TYPE_PIXBUF || + pspec->value_type == G_TYPE_FILE) + { + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (eprop_text->text_entry), + GTK_ENTRY_ICON_SECONDARY, + "document-open-symbolic"); + + g_signal_connect_swapped (eprop_text->text_entry, "icon-release", + G_CALLBACK (glade_eprop_text_show_resource_dialog), + eprop); + } + } + + if (glade_property_def_translatable (property_def)) + { + if (GTK_IS_ENTRY (eprop_text->text_entry)) + { + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (eprop_text->text_entry), + GTK_ENTRY_ICON_SECONDARY, + "document-edit-symbolic"); + g_signal_connect_swapped (eprop_text->text_entry, "icon-release", + G_CALLBACK (glade_eprop_text_show_i18n_dialog), + eprop); + } + else + { + GtkWidget *button = gtk_button_new_from_icon_name ("document-edit-symbolic", + GTK_ICON_SIZE_MENU); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 0); + g_signal_connect_swapped (button, "clicked", + G_CALLBACK (glade_eprop_text_show_i18n_dialog), + eprop); + } + } + return hbox; +} + +/******************************************************************************* + GladeEditorPropertyBoolClass + *******************************************************************************/ +struct _GladeEPropBool +{ + GladeEditorProperty parent_instance; + + GtkWidget *button; +}; + +GLADE_MAKE_EPROP (GladeEPropBool, glade_eprop_bool, GLADE, EPROP_BOOL) + +static void +glade_eprop_bool_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_bool_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property) + { + GladeEPropBool *eprop_bool = GLADE_EPROP_BOOL (eprop); + gboolean state = g_value_get_boolean (glade_property_inline_value (property)); + gtk_switch_set_active (GTK_SWITCH (eprop_bool->button), state); + } +} + +static void +glade_eprop_bool_active_notify (GObject *gobject, + GParamSpec *pspec, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GValue val = { 0, }; + + if (priv->loading) + return; + + g_value_init (&val, G_TYPE_BOOLEAN); + g_value_set_boolean (&val, gtk_switch_get_active (GTK_SWITCH (gobject))); + + glade_editor_property_commit_no_callback (eprop, &val); + + g_value_unset (&val); +} + +static GtkWidget * +glade_eprop_bool_create_input (GladeEditorProperty *eprop) +{ + GladeEPropBool *eprop_bool = GLADE_EPROP_BOOL (eprop); + + eprop_bool->button = gtk_switch_new (); + gtk_widget_set_halign (eprop_bool->button, GTK_ALIGN_START); + gtk_widget_set_valign (eprop_bool->button, GTK_ALIGN_CENTER); + + g_signal_connect (eprop_bool->button, "notify::active", + G_CALLBACK (glade_eprop_bool_active_notify), eprop); + + return eprop_bool->button; +} + +/******************************************************************************* + GladeEditorPropertyCheckClass + *******************************************************************************/ +struct _GladeEPropCheck +{ + GladeEditorProperty parent_instance; + + GtkWidget *button; +}; + +GLADE_MAKE_EPROP (GladeEPropCheck, glade_eprop_check, GLADE, EPROP_CHECK) + +static void +glade_eprop_check_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_check_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property) + { + GladeEPropCheck *eprop_check = GLADE_EPROP_CHECK (eprop); + gboolean state = g_value_get_boolean (glade_property_inline_value (property)); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (eprop_check->button), state); + } +} + +static void +glade_eprop_check_active_notify (GObject *gobject, + GParamSpec *pspec, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GValue val = { 0, }; + + if (priv->loading) + return; + + g_value_init (&val, G_TYPE_BOOLEAN); + g_value_set_boolean (&val, gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (gobject))); + + glade_editor_property_commit_no_callback (eprop, &val); + + g_value_unset (&val); +} + +static GtkWidget * +glade_eprop_check_create_input (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropCheck *eprop_check = GLADE_EPROP_CHECK (eprop); + GladePropertyDef *pclass; + GtkWidget *label; + + pclass = priv->property_def; + + /* Add the property label as the check button's child */ + label = glade_editor_property_get_item_label (eprop); + + glade_property_label_set_property_name (GLADE_PROPERTY_LABEL (label), + glade_property_def_id (pclass)); + glade_property_label_set_packing (GLADE_PROPERTY_LABEL (label), + glade_property_def_get_is_packing (pclass)); + glade_property_label_set_append_colon (GLADE_PROPERTY_LABEL (label), FALSE); + glade_property_label_set_custom_text (GLADE_PROPERTY_LABEL (label), + priv->custom_text); + gtk_widget_show (label); + + eprop_check->button = gtk_check_button_new (); + gtk_widget_set_focus_on_click (eprop_check->button, FALSE); + + gtk_container_add (GTK_CONTAINER (eprop_check->button), label); + + gtk_widget_set_halign (eprop_check->button, GTK_ALIGN_START); + gtk_widget_set_valign (eprop_check->button, GTK_ALIGN_CENTER); + + g_signal_connect (eprop_check->button, "notify::active", + G_CALLBACK (glade_eprop_check_active_notify), eprop); + + return eprop_check->button; +} + +/******************************************************************************* + GladeEditorPropertyUnicharClass + *******************************************************************************/ +struct _GladeEPropUnichar +{ + GladeEditorProperty parent_instance; + + GtkWidget *entry; +}; + +GLADE_MAKE_EPROP (GladeEPropUnichar, glade_eprop_unichar, GLADE, EPROP_UNICHAR) + +static void +glade_eprop_unichar_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_unichar_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEPropUnichar *eprop_unichar = GLADE_EPROP_UNICHAR (eprop); + + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property && GTK_IS_ENTRY (eprop_unichar->entry)) + { + GtkEntry *entry = GTK_ENTRY (eprop_unichar->entry); + gchar utf8st[8]; + gint n; + + if ((n = g_unichar_to_utf8 (g_value_get_uint (glade_property_inline_value (property)), utf8st))) + { + utf8st[n] = '\0'; + gtk_entry_set_text (entry, utf8st); + } + } +} + + +static void +glade_eprop_unichar_changed (GtkWidget *entry, GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + const gchar *text; + + if (priv->loading) + return; + + if ((text = gtk_entry_get_text (GTK_ENTRY (entry))) != NULL) + { + gunichar unich = g_utf8_get_char (text); + GValue val = { 0, }; + + g_value_init (&val, G_TYPE_UINT); + g_value_set_uint (&val, unich); + + glade_editor_property_commit_no_callback (eprop, &val); + + g_value_unset (&val); + } +} + +static void +glade_eprop_unichar_delete (GtkEditable *editable, + gint start_pos, + gint end_pos, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + if (priv->loading) + return; + gtk_editable_select_region (editable, 0, -1); + g_signal_stop_emission_by_name (G_OBJECT (editable), "delete_text"); +} + +static void +glade_eprop_unichar_insert (GtkWidget *entry, + const gchar *text, + gint length, + gint *position, + GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + if (priv->loading) + return; + g_signal_handlers_block_by_func + (G_OBJECT (entry), G_CALLBACK (glade_eprop_unichar_changed), eprop); + g_signal_handlers_block_by_func + (G_OBJECT (entry), G_CALLBACK (glade_eprop_unichar_insert), eprop); + g_signal_handlers_block_by_func + (G_OBJECT (entry), G_CALLBACK (glade_eprop_unichar_delete), eprop); + + gtk_editable_delete_text (GTK_EDITABLE (entry), 0, -1); + *position = 0; + gtk_editable_insert_text (GTK_EDITABLE (entry), text, 1, position); + + g_signal_handlers_unblock_by_func + (G_OBJECT (entry), G_CALLBACK (glade_eprop_unichar_changed), eprop); + g_signal_handlers_unblock_by_func + (G_OBJECT (entry), G_CALLBACK (glade_eprop_unichar_insert), eprop); + g_signal_handlers_unblock_by_func + (G_OBJECT (entry), G_CALLBACK (glade_eprop_unichar_delete), eprop); + + g_signal_stop_emission_by_name (G_OBJECT (entry), "insert_text"); + + glade_eprop_unichar_changed (entry, eprop); +} + +static GtkWidget * +glade_eprop_unichar_create_input (GladeEditorProperty *eprop) +{ + GladeEPropUnichar *eprop_unichar = GLADE_EPROP_UNICHAR (eprop); + + eprop_unichar->entry = gtk_entry_new (); + gtk_widget_set_halign (eprop_unichar->entry, GTK_ALIGN_START); + gtk_widget_set_valign (eprop_unichar->entry, GTK_ALIGN_CENTER); + + /* it's 2 to prevent spirious beeps... */ + gtk_entry_set_max_length (GTK_ENTRY (eprop_unichar->entry), 2); + + g_signal_connect (G_OBJECT (eprop_unichar->entry), "changed", + G_CALLBACK (glade_eprop_unichar_changed), eprop); + g_signal_connect (G_OBJECT (eprop_unichar->entry), "insert_text", + G_CALLBACK (glade_eprop_unichar_insert), eprop); + g_signal_connect (G_OBJECT (eprop_unichar->entry), "delete_text", + G_CALLBACK (glade_eprop_unichar_delete), eprop); + return eprop_unichar->entry; +} + +/******************************************************************************* + GladeEditorPropertyObjectClass + *******************************************************************************/ +enum +{ + OBJ_COLUMN_WIDGET = 0, + OBJ_COLUMN_WIDGET_NAME, + OBJ_COLUMN_WIDGET_CLASS, + OBJ_COLUMN_SELECTED, + OBJ_COLUMN_SELECTABLE, + OBJ_NUM_COLUMNS +}; + +#define GLADE_RESPONSE_CLEAR 42 +#define GLADE_RESPONSE_CREATE 43 + +struct _GladeEPropObject +{ + GladeEditorProperty parent_instance; + + GtkWidget *entry; +}; + +GLADE_MAKE_EPROP (GladeEPropObject, glade_eprop_object, GLADE, EPROP_OBJECT) + +static void +glade_eprop_object_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + + +static gchar * +glade_eprop_object_name (const gchar *name, + GtkTreeStore *model, GtkTreeIter *parent_iter) +{ + GtkTreePath *path; + GString *string; + gint i; + + string = g_string_new (name); + + if (parent_iter) + { + path = gtk_tree_model_get_path (GTK_TREE_MODEL (model), parent_iter); + for (i = 0; i < gtk_tree_path_get_depth (path); i++) + g_string_prepend (string, " "); + } + + return g_string_free (string, FALSE); +} + +static gboolean +search_list (GList * list, gpointer data) +{ + return g_list_find (list, data) != NULL; +} + + +/* + * Note that widgets is a list of GtkWidgets, while what we store + * in the model are the associated GladeWidgets. + */ +static void +glade_eprop_object_populate_view_real (GtkTreeStore *model, + GtkTreeIter *parent_iter, + GList *widgets, + GList *selected_widgets, + GList *exception_widgets, + GType object_type, + gboolean parentless) +{ + GList *children, *list; + GtkTreeIter iter; + gboolean good_type, has_decendant; + + for (list = widgets; list; list = list->next) + { + GladeWidget *widget; + GladeWidgetAdaptor *adaptor; + const gchar *widget_name; + if ((widget = glade_widget_get_from_gobject (list->data)) != NULL) + { + adaptor = glade_widget_get_adaptor (widget); + + has_decendant = + !parentless && glade_widget_has_decendant (widget, object_type); + + good_type = (glade_widget_adaptor_get_object_type (adaptor) == object_type || + g_type_is_a (glade_widget_adaptor_get_object_type (adaptor), object_type)); + + widget_name = glade_widget_get_display_name (widget); + if (parentless) + good_type = good_type && GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor); + + if (good_type || has_decendant) + { + gchar *prop_name = glade_eprop_object_name (widget_name, model, parent_iter); + gtk_tree_store_append (model, &iter, parent_iter); + gtk_tree_store_set + (model, &iter, + OBJ_COLUMN_WIDGET, widget, + OBJ_COLUMN_WIDGET_NAME, + prop_name, + OBJ_COLUMN_WIDGET_CLASS, glade_widget_adaptor_get_title (adaptor), + /* Selectable if its a compatible type and + * its not itself. + */ + OBJ_COLUMN_SELECTABLE, + good_type && !search_list (exception_widgets, widget), + OBJ_COLUMN_SELECTED, + good_type && search_list (selected_widgets, widget), -1); + g_free (prop_name); + } + + if (has_decendant && + (children = glade_widget_adaptor_get_children + (adaptor, glade_widget_get_object (widget))) != NULL) + { + GtkTreeIter *copy = NULL; + + copy = gtk_tree_iter_copy (&iter); + glade_eprop_object_populate_view_real (model, copy, children, + selected_widgets, + exception_widgets, + object_type, parentless); + gtk_tree_iter_free (copy); + + g_list_free (children); + } + } + } +} + +static void +glade_eprop_object_populate_view (GladeProject *project, + GtkTreeView *view, + GList *selected, + GList *exceptions, + GType object_type, + gboolean parentless) +{ + GtkTreeStore *model = (GtkTreeStore *) gtk_tree_view_get_model (view); + GList *list, *toplevels = NULL; + + /* Make a list of only the toplevel widgets */ + for (list = (GList *) glade_project_get_objects (project); list; + list = list->next) + { + GObject *object = G_OBJECT (list->data); + GladeWidget *gwidget = glade_widget_get_from_gobject (object); + g_assert (gwidget); + + if (glade_widget_get_parent (gwidget) == NULL) + toplevels = g_list_append (toplevels, object); + } + + /* add the widgets and recurse */ + glade_eprop_object_populate_view_real (model, NULL, toplevels, selected, + exceptions, object_type, parentless); + g_list_free (toplevels); +} + +static gboolean +glade_eprop_object_clear_iter (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + OBJ_COLUMN_SELECTED, FALSE, -1); + return FALSE; +} + +static gboolean +glade_eprop_object_selected_widget (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GladeWidget **ret) +{ + gboolean selected; + GladeWidget *widget; + + gtk_tree_model_get (model, iter, + OBJ_COLUMN_SELECTED, &selected, + OBJ_COLUMN_WIDGET, &widget, -1); + + if (selected) + { + *ret = widget; + return TRUE; + } + return FALSE; +} + +static void +glade_eprop_object_selected (GtkCellRendererToggle *cell, + gchar *path_str, + GtkTreeModel *model) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (path_str); + GtkTreeIter iter; + gboolean enabled, radio; + + radio = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (model), "radio-list")); + + + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, OBJ_COLUMN_SELECTED, &enabled, -1); + + /* Clear the rest of the view first + */ + if (radio) + gtk_tree_model_foreach (model, glade_eprop_object_clear_iter, NULL); + + gtk_tree_store_set (GTK_TREE_STORE (model), &iter, + OBJ_COLUMN_SELECTED, radio ? TRUE : !enabled, -1); + + gtk_tree_path_free (path); +} + +static GtkWidget * +glade_eprop_object_view (gboolean radio) +{ + GtkWidget *view_widget; + GtkTreeModel *model; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + model = (GtkTreeModel *) gtk_tree_store_new (OBJ_NUM_COLUMNS, G_TYPE_OBJECT, /* The GladeWidget */ + G_TYPE_STRING, /* The GladeWidget's name */ + G_TYPE_STRING, /* The GladeWidgetAdaptor title */ + G_TYPE_BOOLEAN, /* Whether this row is selected or not */ + G_TYPE_BOOLEAN); /* Whether this GladeWidget is + * of an acceptable type and + * therefore can be selected. + */ + + g_object_set_data (G_OBJECT (model), "radio-list", GINT_TO_POINTER (radio)); + + view_widget = gtk_tree_view_new_with_model (model); + gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (view_widget), FALSE); + + /* Pass ownership to the view */ + g_object_unref (G_OBJECT (model)); + g_object_set (G_OBJECT (view_widget), "enable-search", FALSE, NULL); + + /********************* fake invisible column *********************/ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "editable", FALSE, "visible", FALSE, NULL); + + column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view_widget), column); + + gtk_tree_view_column_set_visible (column, FALSE); + gtk_tree_view_set_expander_column (GTK_TREE_VIEW (view_widget), column); + + /************************ selected column ************************/ + renderer = gtk_cell_renderer_toggle_new (); + g_object_set (G_OBJECT (renderer), + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + "activatable", TRUE, "radio", radio, NULL); + g_signal_connect (renderer, "toggled", + G_CALLBACK (glade_eprop_object_selected), model); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW (view_widget), 0, + NULL, renderer, + "visible", OBJ_COLUMN_SELECTABLE, + "sensitive", OBJ_COLUMN_SELECTABLE, "active", OBJ_COLUMN_SELECTED, NULL); + + /********************* widget name column *********************/ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "editable", FALSE, NULL); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW (view_widget), 1, + _("Name"), renderer, "text", OBJ_COLUMN_WIDGET_NAME, NULL); + + /***************** widget class title column ******************/ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "editable", FALSE, + "style", PANGO_STYLE_ITALIC, "foreground", "Gray", NULL); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW (view_widget), 2, + _("Class"), renderer, "text", OBJ_COLUMN_WIDGET_CLASS, NULL); + + return view_widget; +} + + +static gchar * +glade_eprop_object_dialog_title (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + gboolean parentless; + GParamSpec *pspec; + + parentless = glade_property_def_parentless_widget (priv->property_def); + pspec = glade_property_def_get_pspec (priv->property_def); + + if (GLADE_IS_PARAM_SPEC_OBJECTS (pspec)) + { + const gchar *typename = g_type_name (glade_param_spec_objects_get_type (GLADE_PARAM_SPEC_OBJECTS (pspec))); + + if (parentless) + return g_strdup_printf (_("Choose parentless %s type objects in this project"), typename); + else + return g_strdup_printf (_("Choose %s type objects in this project"), typename); + } + else + { + GladeWidgetAdaptor *adaptor; + const gchar *title; + + adaptor = glade_widget_adaptor_get_by_type (pspec->value_type); + + if (adaptor != NULL) + title = glade_widget_adaptor_get_title (adaptor); + else + { + /* Fallback on type name (which would look like "GtkButton" + * instead of "Button" and maybe not translated). + */ + title = g_type_name (pspec->value_type); + } + + if (parentless) + return g_strdup_printf (_("Choose a parentless %s in this project"), title); + else + return g_strdup_printf (_("Choose a %s in this project"), title); + } +} + +gboolean +glade_editor_property_show_object_dialog (GladeProject *project, + const gchar *title, + GtkWidget *parent, + GType object_type, + GladeWidget *exception, + GladeWidget **object) +{ + GtkWidget *dialog; + GtkWidget *vbox, *label, *sw; + GtkWidget *tree_view; + GtkWidget *content_area; + GList *selected_list = NULL, *exception_list = NULL; + gint res; + + g_return_val_if_fail (object != NULL, -1); + + if (!parent) + parent = glade_app_get_window (); + + dialog = gtk_dialog_new_with_buttons (title, + GTK_WINDOW (parent), + GTK_DIALOG_MODAL, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("C_lear"), GLADE_RESPONSE_CLEAR, + _("_OK"), GTK_RESPONSE_OK, NULL); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 500); + + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_show (vbox); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0); + + /* Checklist */ + label = gtk_label_new_with_mnemonic (_("O_bjects:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_set_size_request (sw, 400, 200); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + + if (*object) + selected_list = g_list_prepend (selected_list, *object); + + if (exception) + exception_list = g_list_prepend (exception_list, exception); + + tree_view = glade_eprop_object_view (TRUE); + glade_eprop_object_populate_view (project, + GTK_TREE_VIEW (tree_view), + selected_list, exception_list, + object_type, FALSE); + g_list_free (selected_list); + g_list_free (exception_list); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + gtk_widget_show (tree_view); + gtk_container_add (GTK_CONTAINER (sw), tree_view); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), tree_view); + + /* Run the dialog */ + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_OK) + { + GladeWidget *selected = NULL; + + gtk_tree_model_foreach + (gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)), + (GtkTreeModelForeachFunc) + glade_eprop_object_selected_widget, &selected); + + *object = selected; + } + else if (res == GLADE_RESPONSE_CLEAR) + *object = NULL; + + gtk_widget_destroy (dialog); + + return (res == GTK_RESPONSE_OK || res == GLADE_RESPONSE_CLEAR); +} + + +static void +glade_eprop_object_show_dialog (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GtkWidget *dialog, *parent; + GtkWidget *vbox, *label, *sw; + GtkWidget *tree_view; + GtkWidget *content_area; + GladeProject *project; + GladeWidget *widget; + GParamSpec *pspec; + gchar *title = glade_eprop_object_dialog_title (eprop); + gint res; + GladeWidgetAdaptor *create_adaptor = NULL; + GList *selected_list = NULL, *exception_list = NULL; + + widget = glade_property_get_widget (priv->property); + project = glade_widget_get_project (widget); + parent = gtk_widget_get_toplevel (GTK_WIDGET (eprop)); + pspec = glade_property_def_get_pspec (priv->property_def); + + if (glade_property_def_create_type (priv->property_def)) + create_adaptor = + glade_widget_adaptor_get_by_name (glade_property_def_create_type (priv->property_def)); + if (!create_adaptor && + G_TYPE_IS_INSTANTIATABLE (pspec->value_type) && !G_TYPE_IS_ABSTRACT (pspec->value_type)) + create_adaptor = glade_widget_adaptor_get_by_type (pspec->value_type); + + if (create_adaptor) + { + dialog = gtk_dialog_new_with_buttons (title, + GTK_WINDOW (parent), + GTK_DIALOG_MODAL, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("C_lear"), GLADE_RESPONSE_CLEAR, + _("_New"), GLADE_RESPONSE_CREATE, + _("_OK"), GTK_RESPONSE_OK, NULL); + g_free (title); + } + else + { + dialog = gtk_dialog_new_with_buttons (title, + GTK_WINDOW (parent), + GTK_DIALOG_MODAL, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("C_lear"), GLADE_RESPONSE_CLEAR, + _("_OK"), GTK_RESPONSE_OK, NULL); + g_free (title); + } + + gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 500); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_show (vbox); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), 5); + + gtk_box_pack_start (GTK_BOX (content_area), vbox, TRUE, TRUE, 0); + + /* Checklist */ + label = gtk_label_new_with_mnemonic (_("O_bjects:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_set_size_request (sw, 400, 200); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + + exception_list = g_list_prepend (exception_list, widget); + if (g_value_get_object (glade_property_inline_value (priv->property))) + selected_list = g_list_prepend (selected_list, + glade_widget_get_from_gobject + (g_value_get_object + (glade_property_inline_value (priv->property)))); + + tree_view = glade_eprop_object_view (TRUE); + glade_eprop_object_populate_view (project, GTK_TREE_VIEW (tree_view), + selected_list, exception_list, + pspec->value_type, + glade_property_def_parentless_widget (priv->property_def)); + g_list_free (selected_list); + g_list_free (exception_list); + + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + gtk_widget_show (tree_view); + gtk_container_add (GTK_CONTAINER (sw), tree_view); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), tree_view); + + + /* Run the dialog */ + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_OK) + { + GladeWidget *selected = NULL; + + gtk_tree_model_foreach + (gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)), + (GtkTreeModelForeachFunc) + glade_eprop_object_selected_widget, &selected); + + if (selected) + { + GValue *value; + GObject *new_object, *old_object = NULL; + GladeWidget *new_widget; + + glade_project_selection_set (project, + glade_widget_get_object (widget), + TRUE); + + value = glade_property_def_make_gvalue_from_string + (priv->property_def, glade_widget_get_name (selected), project); + + glade_property_get (priv->property, &old_object); + new_object = g_value_get_object (value); + new_widget = glade_widget_get_from_gobject (new_object); + + glade_command_push_group (_("Setting %s of %s to %s"), + glade_property_def_get_name (priv->property_def), + glade_widget_get_name (widget), + glade_widget_get_name (new_widget)); + + /* Unparent the widget so we can reuse it for this property */ + if (glade_property_def_parentless_widget (priv->property_def)) + { + GladeProperty *old_ref; + + if (!G_IS_PARAM_SPEC_OBJECT (pspec)) + g_warning ("Parentless widget property should be of object type"); + else if (new_object && old_object != new_object) + { + /* Steal parentless reference widget references, basically some references + * can only be referenced by one property, here we clear it if such a reference + * exists for the target object + */ + if ((old_ref = glade_widget_get_parentless_widget_ref (new_widget))) + glade_command_set_property (old_ref, NULL); + } + } + + /* Ensure that the object we will now refer to has an ID, the NULL + * check is just paranoia, it *always* has a name. + * + * To refer to a widget, it needs to have a name. + */ + glade_widget_ensure_name (new_widget, project, TRUE); + + glade_editor_property_commit (eprop, value); + glade_command_pop_group (); + + g_value_unset (value); + g_free (value); + } + } + else if (res == GLADE_RESPONSE_CREATE) + { + GladeWidget *new_widget; + + /* translators: Creating 'a widget' for 'a property' of 'a widget' */ + glade_command_push_group (_("Creating %s for %s of %s"), + glade_widget_adaptor_get_display_name (create_adaptor), + glade_property_def_get_name (priv->property_def), + glade_widget_get_name (widget)); + + /* Dont bother if the user canceled the widget */ + if ((new_widget = + glade_command_create (create_adaptor, NULL, NULL, project)) != NULL) + { + GValue *value; + + glade_project_selection_set (project, glade_widget_get_object (widget), TRUE); + + /* Give the newly created object a name */ + glade_widget_ensure_name (new_widget, project, TRUE); + + value = g_new0 (GValue, 1); + g_value_init (value, pspec->value_type); + g_value_set_object (value, glade_widget_get_object (new_widget)); + + glade_editor_property_commit (eprop, value); + + g_value_unset (value); + g_free (value); + } + + glade_command_pop_group (); + } + else if (res == GLADE_RESPONSE_CLEAR) + { + GValue *value = + glade_property_def_make_gvalue_from_string (priv->property_def, NULL, project); + + glade_editor_property_commit (eprop, value); + + g_value_unset (value); + g_free (value); + } + + gtk_widget_destroy (dialog); +} + + +static void +glade_eprop_object_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropObject *eprop_object = GLADE_EPROP_OBJECT (eprop); + gchar *obj_name; + + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property == NULL) + return; + + if ((obj_name = glade_widget_adaptor_string_from_value + (glade_property_def_get_adaptor (priv->property_def), + priv->property_def, glade_property_inline_value (property))) != NULL) + { + gtk_entry_set_text (GTK_ENTRY (eprop_object->entry), obj_name); + g_free (obj_name); + } + else + gtk_entry_set_text (GTK_ENTRY (eprop_object->entry), ""); + +} + +static GtkWidget * +glade_eprop_object_create_input (GladeEditorProperty *eprop) +{ + GladeEPropObject *eprop_object = GLADE_EPROP_OBJECT (eprop); + + eprop_object->entry = gtk_entry_new (); + gtk_widget_set_hexpand (eprop_object->entry, TRUE); + gtk_widget_set_valign (eprop_object->entry, GTK_ALIGN_CENTER); + gtk_editable_set_editable (GTK_EDITABLE (eprop_object->entry), FALSE); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (eprop_object->entry), + GTK_ENTRY_ICON_SECONDARY, + "document-edit-symbolic"); + g_signal_connect_swapped (eprop_object->entry, "icon-release", + G_CALLBACK (glade_eprop_object_show_dialog), eprop); + + return eprop_object->entry; +} + + +/******************************************************************************* + GladeEditorPropertyObjectsClass + *******************************************************************************/ + +struct _GladeEPropObjects +{ + GladeEditorProperty parent_instance; + + GtkWidget *entry; +}; + +GLADE_MAKE_EPROP (GladeEPropObjects, glade_eprop_objects, GLADE, EPROP_OBJECTS) + +static void +glade_eprop_objects_finalize (GObject *object) +{ + /* Chain up */ + G_OBJECT_CLASS (editor_property_class)->finalize (object); +} + +static void +glade_eprop_objects_load (GladeEditorProperty *eprop, GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeEPropObjects *eprop_objects = GLADE_EPROP_OBJECTS (eprop); + gchar *obj_name; + + /* Chain up first */ + editor_property_class->load (eprop, property); + + if (property == NULL) + return; + + if ((obj_name = glade_widget_adaptor_string_from_value + (glade_property_def_get_adaptor (priv->property_def), + priv->property_def, glade_property_inline_value (property))) != NULL) + { + gtk_entry_set_text (GTK_ENTRY (eprop_objects->entry), obj_name); + g_free (obj_name); + } + else + gtk_entry_set_text (GTK_ENTRY (eprop_objects->entry), ""); + +} + +static gboolean +glade_eprop_objects_selected_widget (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GList **ret) +{ + gboolean selected; + GladeWidget *widget; + + gtk_tree_model_get (model, iter, + OBJ_COLUMN_SELECTED, &selected, + OBJ_COLUMN_WIDGET, &widget, -1); + + + if (selected) + { + *ret = g_list_append (*ret, glade_widget_get_object (widget)); + g_object_unref (widget); + } + + return FALSE; +} + +static void +glade_eprop_objects_show_dialog (GladeEditorProperty *eprop) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GtkWidget *dialog, *parent; + GtkWidget *vbox, *label, *sw; + GtkWidget *tree_view; + GladeWidget *widget; + GladeProject *project; + GParamSpec *pspec; + gchar *title = glade_eprop_object_dialog_title (eprop); + gint res; + GList *selected_list = NULL, *exception_list = NULL, *selected_objects = NULL, *l; + + /* It's improbable but possible the editor is visible with no + * property selected, in this case avoid crashes */ + if (!priv->property) + return; + + widget = glade_property_get_widget (priv->property); + project = glade_widget_get_project (widget); + parent = gtk_widget_get_toplevel (GTK_WIDGET (eprop)); + pspec = glade_property_def_get_pspec (priv->property_def); + + dialog = gtk_dialog_new_with_buttons (title, + GTK_WINDOW (parent), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + _("C_lear"), GLADE_RESPONSE_CLEAR, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, NULL); + g_free (title); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 500); + + _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_show (vbox); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + + gtk_box_pack_start (GTK_BOX + (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), vbox, + TRUE, TRUE, 0); + + /* Checklist */ + label = gtk_label_new (_("Objects:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_set_size_request (sw, 400, 200); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + tree_view = glade_eprop_object_view (FALSE); + + /* Dont allow selecting the widget owning this property (perhaps this is wrong) */ + exception_list = g_list_prepend (exception_list, widget); + + /* Build the list of already selected objects */ + glade_property_get (priv->property, &selected_objects); + for (l = selected_objects; l; l = l->next) + selected_list = g_list_prepend (selected_list, glade_widget_get_from_gobject (l->data)); + + glade_eprop_object_populate_view (project, GTK_TREE_VIEW (tree_view), + selected_list, exception_list, + glade_param_spec_objects_get_type (GLADE_PARAM_SPEC_OBJECTS (pspec)), + glade_property_def_parentless_widget (priv->property_def)); + g_list_free (selected_list); + g_list_free (exception_list); + + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + gtk_widget_show (tree_view); + gtk_container_add (GTK_CONTAINER (sw), tree_view); + + /* Run the dialog */ + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_OK) + { + GValue *value; + GList *selected = NULL, *l; + + gtk_tree_model_foreach + (gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)), + (GtkTreeModelForeachFunc) + glade_eprop_objects_selected_widget, &selected); + + if (selected) + { + glade_command_push_group (_("Setting %s of %s"), + glade_property_def_get_name (priv->property_def), + glade_widget_get_name (widget)); + + /* Make sure the selected widgets have names now + */ + for (l = selected; l; l = l->next) + { + GObject *object = l->data; + GladeWidget *selected_widget = glade_widget_get_from_gobject (object); + + glade_widget_ensure_name (selected_widget, project, TRUE); + } + } + + value = glade_property_def_make_gvalue (priv->property_def, selected); + glade_editor_property_commit (eprop, value); + + if (selected) + glade_command_pop_group (); + + g_value_unset (value); + g_free (value); + } + else if (res == GLADE_RESPONSE_CLEAR) + { + GValue *value = glade_property_def_make_gvalue (priv->property_def, NULL); + + glade_editor_property_commit (eprop, value); + + g_value_unset (value); + g_free (value); + } + gtk_widget_destroy (dialog); +} + +static GtkWidget * +glade_eprop_objects_create_input (GladeEditorProperty *eprop) +{ + GladeEPropObjects *eprop_objects = GLADE_EPROP_OBJECTS (eprop); + + eprop_objects->entry = gtk_entry_new (); + gtk_widget_set_hexpand (eprop_objects->entry, TRUE); + gtk_widget_set_valign (eprop_objects->entry, GTK_ALIGN_CENTER); + gtk_editable_set_editable (GTK_EDITABLE (eprop_objects->entry), FALSE); + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (eprop_objects->entry), + GTK_ENTRY_ICON_SECONDARY, + "document-edit-symbolic"); + g_signal_connect_swapped (eprop_objects->entry, "icon-release", + G_CALLBACK (glade_eprop_objects_show_dialog), eprop); + + return eprop_objects->entry; +} + +/******************************************************************************* + API + *******************************************************************************/ +/** + * glade_editor_property_commit: + * @eprop: A #GladeEditorProperty + * @value: The #GValue to commit + * + * Commits @value to the property currently being edited by @eprop. + * + */ +void +glade_editor_property_commit (GladeEditorProperty *eprop, GValue *value) +{ + g_return_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop)); + g_return_if_fail (G_IS_VALUE (value)); + + g_signal_emit (G_OBJECT (eprop), glade_eprop_signals[COMMIT], 0, value); +} + +/** + * glade_editor_property_load: + * @eprop: A #GladeEditorProperty + * @property: A #GladeProperty + * + * Loads @property values into @eprop and connects. + * (the editor property will watch the property's value + * until its loaded with another property or %NULL) + */ +void +glade_editor_property_load (GladeEditorProperty *eprop, + GladeProperty *property) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + + g_return_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop)); + g_return_if_fail (property == NULL || GLADE_IS_PROPERTY (property)); + + priv->loading = TRUE; + GLADE_EDITOR_PROPERTY_GET_CLASS (eprop)->load (eprop, property); + priv->loading = FALSE; +} + + +/** + * glade_editor_property_load_by_widget: + * @eprop: A #GladeEditorProperty + * @widget: A #GladeWidget + * + * Convenience function to load the appropriate #GladeProperty into + * @eprop from @widget + */ +void +glade_editor_property_load_by_widget (GladeEditorProperty *eprop, + GladeWidget *widget) +{ + GladeEditorPropertyPrivate *priv = glade_editor_property_get_instance_private (eprop); + GladeProperty *property = NULL; + + g_return_if_fail (GLADE_IS_EDITOR_PROPERTY (eprop)); + g_return_if_fail (widget == NULL || GLADE_IS_WIDGET (widget)); + + if (widget) + { + /* properties are allowed to be missing on some internal widgets */ + if (glade_property_def_get_is_packing (priv->property_def)) + property = glade_widget_get_pack_property (widget, glade_property_def_id (priv->property_def)); + else + property = glade_widget_get_property (widget, glade_property_def_id (priv->property_def)); + + glade_editor_property_load (eprop, property); + + if (priv->item_label) + glade_property_label_set_property (GLADE_PROPERTY_LABEL (priv->item_label), property); + + if (property) + { + g_assert (priv->property_def == glade_property_get_def (property)); + + gtk_widget_show (GTK_WIDGET (eprop)); + + if (priv->item_label) + gtk_widget_show (priv->item_label); + } + else + { + gtk_widget_hide (GTK_WIDGET (eprop)); + + if (priv->item_label) + gtk_widget_hide (priv->item_label); + } + } + else + glade_editor_property_load (eprop, NULL); +} diff --git a/gladeui/glade-editor-property.h b/gladeui/glade-editor-property.h new file mode 100644 index 0000000..1cef31d --- /dev/null +++ b/gladeui/glade-editor-property.h @@ -0,0 +1,115 @@ +#ifndef __GLADE_EDITOR_PROPERTY_H__ +#define __GLADE_EDITOR_PROPERTY_H__ + +#include + +G_BEGIN_DECLS + +/******************************************************************************* + Boiler plate macros (inspired from glade-command.c) + *******************************************************************************/ +/* XXX document me ! */ + +#define GLADE_MAKE_EPROP(type, func, MODULE, OBJ_NAME) \ +G_DECLARE_FINAL_TYPE (type, func, MODULE, OBJ_NAME, GladeEditorProperty) \ +G_DEFINE_TYPE (type, func, GLADE_TYPE_EDITOR_PROPERTY) \ +static void \ +func ## _finalize (GObject *object); \ +static void \ +func ## _load (GladeEditorProperty *me, GladeProperty *property); \ +static GtkWidget * \ +func ## _create_input (GladeEditorProperty *me); \ +static void \ +func ## _class_init (type ## Class *klass) \ +{ \ + GladeEditorPropertyClass *ep_class = GLADE_EDITOR_PROPERTY_CLASS (klass); \ + GObjectClass* object_class = G_OBJECT_CLASS (klass); \ + ep_class->load = func ## _load; \ + ep_class->create_input = func ## _create_input; \ + object_class->finalize = func ## _finalize; \ +} \ +static void \ +func ## _init (type *self) \ +{ \ +} + +#define GLADE_TYPE_EDITOR_PROPERTY glade_editor_property_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeEditorProperty, glade_editor_property, GLADE, EDITOR_PROPERTY, GtkBox) + +struct _GladeEditorPropertyClass { + GtkBoxClass parent_class; + + void (* load) (GladeEditorProperty *eprop, GladeProperty *property); + GtkWidget *(* create_input) (GladeEditorProperty *eprop); + void (* commit) (GladeEditorProperty *eprop, GValue *value); + void *(* changed) (GladeEditorProperty *eprop, GladeProperty *property); + + gpointer padding[4]; +}; + +void glade_editor_property_load (GladeEditorProperty *eprop, + GladeProperty *property); + +void glade_editor_property_load_by_widget (GladeEditorProperty *eprop, + GladeWidget *widget); + +void glade_editor_property_commit (GladeEditorProperty *eprop, + GValue *value); +void glade_editor_property_commit_no_callback (GladeEditorProperty *eprop, + GValue *value); +void glade_editor_property_set_custom_text (GladeEditorProperty *eprop, + const gchar *custom_text); +const gchar *glade_editor_property_get_custom_text (GladeEditorProperty *eprop); +void glade_editor_property_set_disable_check (GladeEditorProperty *eprop, + gboolean disable_check); +gboolean glade_editor_property_get_disable_check (GladeEditorProperty *eprop); + +GtkWidget *glade_editor_property_get_item_label (GladeEditorProperty *eprop); +GladePropertyDef *glade_editor_property_get_property_def (GladeEditorProperty *eprop); +GladeProperty *glade_editor_property_get_property (GladeEditorProperty *eprop); +gboolean glade_editor_property_loading (GladeEditorProperty *eprop); + +gboolean glade_editor_property_show_i18n_dialog (GtkWidget *parent, + gchar **text, + gchar **context, + gchar **comment, + gboolean *translatable); +gboolean glade_editor_property_show_resource_dialog (GladeProject *project, + GtkWidget *parent, + gchar **filename); + +gboolean glade_editor_property_show_object_dialog (GladeProject *project, + const gchar *title, + GtkWidget *parent, + GType object_type, + GladeWidget *exception, + GladeWidget **object); + +/* Generic eprops */ +#define GLADE_TYPE_EPROP_NUMERIC (glade_eprop_numeric_get_type()) +#define GLADE_TYPE_EPROP_ENUM (glade_eprop_enum_get_type()) +#define GLADE_TYPE_EPROP_FLAGS (glade_eprop_flags_get_type()) +#define GLADE_TYPE_EPROP_COLOR (glade_eprop_color_get_type()) +#define GLADE_TYPE_EPROP_NAMED_ICON (glade_eprop_named_icon_get_type()) +#define GLADE_TYPE_EPROP_TEXT (glade_eprop_text_get_type()) +#define GLADE_TYPE_EPROP_BOOL (glade_eprop_bool_get_type()) +#define GLADE_TYPE_EPROP_CHECK (glade_eprop_check_get_type()) +#define GLADE_TYPE_EPROP_UNICHAR (glade_eprop_unichar_get_type()) +#define GLADE_TYPE_EPROP_OBJECT (glade_eprop_object_get_type()) +#define GLADE_TYPE_EPROP_OBJECTS (glade_eprop_objects_get_type()) +GType glade_eprop_numeric_get_type (void) G_GNUC_CONST; +GType glade_eprop_enum_get_type (void) G_GNUC_CONST; +GType glade_eprop_flags_get_type (void) G_GNUC_CONST; +GType glade_eprop_color_get_type (void) G_GNUC_CONST; +GType glade_eprop_named_icon_get_type (void) G_GNUC_CONST; +GType glade_eprop_text_get_type (void) G_GNUC_CONST; +GType glade_eprop_bool_get_type (void) G_GNUC_CONST; +GType glade_eprop_check_get_type (void) G_GNUC_CONST; +GType glade_eprop_unichar_get_type (void) G_GNUC_CONST; +GType glade_eprop_object_get_type (void) G_GNUC_CONST; +GType glade_eprop_objects_get_type (void) G_GNUC_CONST; + + +G_END_DECLS + +#endif /* __GLADE_EDITOR_PROPERTY_H__ */ diff --git a/gladeui/glade-editor-skeleton.c b/gladeui/glade-editor-skeleton.c new file mode 100644 index 0000000..9e61534 --- /dev/null +++ b/gladeui/glade-editor-skeleton.c @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-popup.h" +#include "glade-editable.h" +#include "glade-editor-skeleton.h" + +/* GObjectClass */ +static void glade_editor_skeleton_dispose (GObject *object); + +/* GladeEditableInterface */ +static void glade_editor_skeleton_editable_init (GladeEditableInterface *iface); + +/* GtkBuildableIface */ +static void glade_editor_skeleton_buildable_init (GtkBuildableIface *iface); + +typedef struct _GladeEditorSkeletonPrivate GladeEditorSkeletonPrivate; + +struct _GladeEditorSkeletonPrivate +{ + GSList *editors; +}; + +static GladeEditableInterface *parent_editable_iface; +static GtkBuildableIface *parent_buildable_iface; + +G_DEFINE_TYPE_WITH_CODE (GladeEditorSkeleton, glade_editor_skeleton, GTK_TYPE_BOX, + G_ADD_PRIVATE (GladeEditorSkeleton) + G_IMPLEMENT_INTERFACE (GLADE_TYPE_EDITABLE, + glade_editor_skeleton_editable_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_BUILDABLE, + glade_editor_skeleton_buildable_init)) + +static void +glade_editor_skeleton_init (GladeEditorSkeleton *skeleton) +{ + +} + +static void +glade_editor_skeleton_class_init (GladeEditorSkeletonClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = glade_editor_skeleton_dispose; +} + +/*********************************************************** + * GObjectClass * + ***********************************************************/ +static void +glade_editor_skeleton_dispose (GObject *object) +{ + GladeEditorSkeleton *skeleton = GLADE_EDITOR_SKELETON (object); + GladeEditorSkeletonPrivate *priv = glade_editor_skeleton_get_instance_private (skeleton); + + if (priv->editors) + { + g_slist_free_full (priv->editors, (GDestroyNotify)g_object_unref); + priv->editors = NULL; + } + + G_OBJECT_CLASS (glade_editor_skeleton_parent_class)->dispose (object); +} + +/******************************************************************************* + * GladeEditableInterface * + *******************************************************************************/ +static void +glade_editor_skeleton_load (GladeEditable *editable, + GladeWidget *widget) +{ + GladeEditorSkeleton *skeleton = GLADE_EDITOR_SKELETON (editable); + GladeEditorSkeletonPrivate *priv = glade_editor_skeleton_get_instance_private (skeleton); + GSList *l; + + /* Chain up to default implementation */ + parent_editable_iface->load (editable, widget); + + for (l = priv->editors; l; l = l->next) + { + GladeEditable *editor = l->data; + + glade_editable_load (editor, widget); + } +} + +static void +glade_editor_skeleton_set_show_name (GladeEditable *editable, gboolean show_name) +{ + GladeEditorSkeleton *skeleton = GLADE_EDITOR_SKELETON (editable); + GladeEditorSkeletonPrivate *priv = glade_editor_skeleton_get_instance_private (skeleton); + GSList *l; + + for (l = priv->editors; l; l = l->next) + { + GladeEditable *editor = l->data; + + glade_editable_set_show_name (editor, show_name); + } +} + +static void +glade_editor_skeleton_editable_init (GladeEditableInterface *iface) +{ + parent_editable_iface = g_type_default_interface_peek (GLADE_TYPE_EDITABLE); + + iface->load = glade_editor_skeleton_load; + iface->set_show_name = glade_editor_skeleton_set_show_name; +} + +/******************************************************************************* + * GtkBuildableIface * + *******************************************************************************/ +typedef struct +{ + GSList *editors; +} EditorParserData; + +static void +editor_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **names, + const gchar **values, + gpointer user_data, + GError **error) +{ + EditorParserData *editor_data = (EditorParserData *)user_data; + gchar *id; + + if (strcmp (element_name, "editor") == 0) + { + if (g_markup_collect_attributes (element_name, + names, + values, + error, + G_MARKUP_COLLECT_STRDUP, "id", &id, + G_MARKUP_COLLECT_INVALID)) + { + editor_data->editors = g_slist_append (editor_data->editors, id); + } + } + else if (strcmp (element_name, "child-editors") == 0) + ; + else + g_warning ("Unsupported tag for GladeEditorSkeleton: %s\n", element_name); +} + +static const GMarkupParser editor_parser = + { + editor_start_element, + }; + +static gboolean +glade_editor_skeleton_custom_tag_start (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + GMarkupParser *parser, + gpointer *data) +{ + if (strcmp (tagname, "child-editors") == 0) + { + EditorParserData *parser_data; + + parser_data = g_slice_new0 (EditorParserData); + *parser = editor_parser; + *data = parser_data; + return TRUE; + } + + return parent_buildable_iface->custom_tag_start (buildable, builder, child, + tagname, parser, data); +} + +static void +glade_editor_skeleton_custom_finished (GtkBuildable *buildable, + GtkBuilder *builder, + GObject *child, + const gchar *tagname, + gpointer user_data) +{ + EditorParserData *editor_data = (EditorParserData *)user_data; + GSList *l; + + if (strcmp (tagname, "child-editors") != 0) + { + parent_buildable_iface->custom_finished (buildable, builder, child, + tagname, user_data); + return; + } + + for (l = editor_data->editors; l; l = l->next) + { + GObject *object; + gchar *id = l->data; + + object = gtk_builder_get_object (builder, id); + + if (!GLADE_EDITABLE (object)) + g_warning ("Object '%s' is not a GladeEditable\n", + object ? G_OBJECT_TYPE_NAME (object) : "(null)"); + else + glade_editor_skeleton_add_editor (GLADE_EDITOR_SKELETON (buildable), + GLADE_EDITABLE (object)); + } + + g_slist_free_full (editor_data->editors, g_free); + g_slice_free (EditorParserData, editor_data); +} + +static void +glade_editor_skeleton_buildable_init (GtkBuildableIface *iface) +{ + parent_buildable_iface = g_type_interface_peek_parent (iface); + iface->custom_tag_start = glade_editor_skeleton_custom_tag_start; + iface->custom_finished = glade_editor_skeleton_custom_finished; +} + +/******************************************************************************* + * API * + *******************************************************************************/ +GtkWidget * +glade_editor_skeleton_new (void) +{ + return g_object_new (GLADE_TYPE_EDITOR_SKELETON, NULL); +} + +void +glade_editor_skeleton_add_editor (GladeEditorSkeleton *skeleton, + GladeEditable *editor) +{ + GladeEditorSkeletonPrivate *priv = glade_editor_skeleton_get_instance_private (skeleton); + + g_return_if_fail (GLADE_IS_EDITOR_SKELETON (skeleton)); + g_return_if_fail (GLADE_IS_EDITABLE (editor)); + + g_object_ref (editor); + priv->editors = g_slist_prepend (priv->editors, editor); +} diff --git a/gladeui/glade-editor-skeleton.h b/gladeui/glade-editor-skeleton.h new file mode 100644 index 0000000..1e6d22b --- /dev/null +++ b/gladeui/glade-editor-skeleton.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ +#ifndef __GLADE_EDITOR_SKELETON_H__ +#define __GLADE_EDITOR_SKELETON_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_EDITOR_SKELETON glade_editor_skeleton_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeEditorSkeleton, glade_editor_skeleton, GLADE, EDITOR_SKELETON, GtkBox) + +struct _GladeEditorSkeletonClass +{ + GtkBoxClass parent_class; +}; + +GtkWidget *glade_editor_skeleton_new (void); +void glade_editor_skeleton_add_editor (GladeEditorSkeleton *skeleton, + GladeEditable *editor); + +G_END_DECLS + +#endif /* __GLADE_EDITOR_SKELETON_H__ */ diff --git a/gladeui/glade-editor-table.c b/gladeui/glade-editor-table.c new file mode 100644 index 0000000..48b6aad --- /dev/null +++ b/gladeui/glade-editor-table.c @@ -0,0 +1,667 @@ +/* + * Copyright (C) 2008 Tristan Van Berkom. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#include +#include +#include "glade.h" +#include "glade-private.h" +#include "gladeui-enum-types.h" + +#include "glade-editor-table.h" + + +#define BLOCK_NAME_ENTRY_CB(table) \ + do { if (priv->name_entry) \ + g_signal_handlers_block_by_func (G_OBJECT (priv->name_entry), \ + G_CALLBACK (widget_name_edited), table); \ + } while (0); + +#define UNBLOCK_NAME_ENTRY_CB(table) \ + do { if (priv->name_entry) \ + g_signal_handlers_unblock_by_func (G_OBJECT (priv->name_entry), \ + G_CALLBACK (widget_name_edited), table); \ + } while (0); + + + +static void glade_editor_table_init (GladeEditorTable *self); +static void glade_editor_table_class_init (GladeEditorTableClass *klass); + +static void glade_editor_table_dispose (GObject *object); +static void glade_editor_table_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +static void glade_editor_table_editable_init (GladeEditableInterface *iface); +static void glade_editor_table_realize (GtkWidget *widget); +static void glade_editor_table_grab_focus (GtkWidget *widget); + +static void append_name_field (GladeEditorTable *table); +static void append_items (GladeEditorTable *table, + GladeWidgetAdaptor *adaptor, + GladeEditorPageType type); + +typedef struct _GladeEditorTablePrivate GladeEditorTablePrivate; +struct _GladeEditorTablePrivate +{ + GladeWidgetAdaptor *adaptor; /* The GladeWidgetAdaptor this + * table was created for. + */ + + GladeWidget *loaded_widget; /* A pointer to the currently loaded GladeWidget + */ + + GtkWidget *name_label; /* A pointer to the "Name:" label (for show/hide) */ + GtkWidget *name_entry; /* A pointer to the gtk_entry that holds + * the name of the widget. This is the + * first item _pack'ed to the table_widget. + * We have a pointer here because it is an + * entry which will not be created from a + * GladeProperty but rather from code. + */ + GtkWidget *composite_check; /* A pointer to the composite check button */ + GtkWidget *name_field; /* A box containing the name entry and composite check */ + + GList *properties; /* A list of GladeEditorPropery items. + * For each row in the gtk_table, there is a + * corresponding GladeEditorProperty struct. + */ + + GladeEditorPageType type; /* Is this table to be used in the common tab, ? + * the general tab, a packing tab or the query popup ? + */ + + gint rows; + + gboolean show_name; +}; + +enum { + PROP_0, + PROP_PAGE_TYPE, +}; + +G_DEFINE_TYPE_WITH_CODE (GladeEditorTable, glade_editor_table, GTK_TYPE_GRID, + G_ADD_PRIVATE (GladeEditorTable) + G_IMPLEMENT_INTERFACE (GLADE_TYPE_EDITABLE, + glade_editor_table_editable_init)) + +static void +glade_editor_table_class_init (GladeEditorTableClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = glade_editor_table_dispose; + object_class->set_property = glade_editor_table_set_property; + + widget_class->realize = glade_editor_table_realize; + widget_class->grab_focus = glade_editor_table_grab_focus; + + g_object_class_install_property + (object_class, PROP_PAGE_TYPE, + g_param_spec_enum ("page-type", _("Page Type"), + _("The editor page type to create this GladeEditorTable for"), + GLADE_TYPE_EDITOR_PAGE_TYPE, GLADE_PAGE_GENERAL, + G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); +} + +static void +glade_editor_table_init (GladeEditorTable *self) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (self); + + gtk_orientable_set_orientation (GTK_ORIENTABLE (self), + GTK_ORIENTATION_VERTICAL); + gtk_grid_set_row_spacing (GTK_GRID (self), 2); + gtk_grid_set_column_spacing (GTK_GRID (self), 6); + + /* Show name by default */ + priv->show_name = TRUE; +} + +static void +glade_editor_table_dispose (GObject *object) +{ + GladeEditorTable *table = GLADE_EDITOR_TABLE (object); + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + + priv->properties = (g_list_free (priv->properties), NULL); + + /* the entry is finalized anyway, just avoid setting + * text in it from _load(); + */ + priv->name_entry = NULL; + + glade_editable_load (GLADE_EDITABLE (table), NULL); + + G_OBJECT_CLASS (glade_editor_table_parent_class)->dispose (object); +} + +static void +glade_editor_table_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeEditorTable *table = GLADE_EDITOR_TABLE (object); + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + + switch (prop_id) + { + case PROP_PAGE_TYPE: + priv->type = g_value_get_enum (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_editor_table_realize (GtkWidget *widget) +{ + GladeEditorTable *table = GLADE_EDITOR_TABLE (widget); + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + GList *list; + GladeEditorProperty *property; + + GTK_WIDGET_CLASS (glade_editor_table_parent_class)->realize (widget); + + /* Sync up properties, even if widget is NULL */ + for (list = priv->properties; list; list = list->next) + { + property = list->data; + glade_editor_property_load_by_widget (property, priv->loaded_widget); + } +} + +static void +glade_editor_table_grab_focus (GtkWidget *widget) +{ + GladeEditorTable *editor_table = GLADE_EDITOR_TABLE (widget); + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (editor_table); + + if (priv->name_entry && + gtk_widget_get_mapped (priv->name_entry)) + gtk_widget_grab_focus (priv->name_entry); + else if (priv->properties) + gtk_widget_grab_focus (GTK_WIDGET (priv->properties->data)); + else + GTK_WIDGET_CLASS (glade_editor_table_parent_class)->grab_focus (widget); +} + +static void +widget_name_edited (GtkWidget *editable, GladeEditorTable *table) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + GladeWidget *widget; + gchar *new_name; + + g_return_if_fail (GTK_IS_EDITABLE (editable)); + g_return_if_fail (GLADE_IS_EDITOR_TABLE (table)); + + if (priv->loaded_widget == NULL) + { + g_warning ("Name entry edited with no loaded widget in editor %p!\n", + table); + return; + } + + widget = priv->loaded_widget; + new_name = gtk_editable_get_chars (GTK_EDITABLE (editable), 0, -1); + + if (new_name == NULL || new_name[0] == '\0') + { + /* If we are explicitly trying to set the widget name to be empty, + * then we must not allow it there are any active references to + * the widget which would otherwise break. + * + * Otherwise, we need to allocate a new unnamed prefix name for the widget + */ + if (!glade_widget_has_prop_refs (widget)) + { + gchar *unnamed_name = glade_project_new_widget_name (glade_widget_get_project (widget), NULL, GLADE_UNNAMED_PREFIX); + glade_command_set_name (widget, unnamed_name); + g_free (unnamed_name); + } + } + else if (glade_project_available_widget_name (glade_widget_get_project (widget), + widget, new_name)) + glade_command_set_name (widget, new_name); + + g_free (new_name); +} + +static void +widget_composite_toggled (GtkToggleButton *composite_check, + GladeEditorTable *table) +{ + GladeProject *project; + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + + if (priv->loaded_widget == NULL) + { + g_warning ("Name entry edited with no loaded widget in editor %p!\n", + table); + return; + } + + project = glade_widget_get_project (priv->loaded_widget); + + if (project) + { + if (gtk_toggle_button_get_active (composite_check)) + glade_command_set_project_template (project, priv->loaded_widget); + else + glade_command_set_project_template (project, NULL); + } +} + +static void +widget_name_changed (GladeWidget *widget, + GParamSpec *pspec, + GladeEditorTable *table) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + + if (!gtk_widget_get_mapped (GTK_WIDGET (table))) + return; + + if (priv->name_entry) + { + BLOCK_NAME_ENTRY_CB (table); + + if (glade_widget_has_name (priv->loaded_widget)) + gtk_entry_set_text (GTK_ENTRY (priv->name_entry), glade_widget_get_name (priv->loaded_widget)); + else + gtk_entry_set_text (GTK_ENTRY (priv->name_entry), ""); + + UNBLOCK_NAME_ENTRY_CB (table); + } +} + +static void +widget_composite_changed (GladeWidget *widget, + GParamSpec *pspec, + GladeEditorTable *table) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + if (!gtk_widget_get_mapped (GTK_WIDGET (table))) + return; + + if (priv->name_label) + gtk_label_set_text (GTK_LABEL (priv->name_label), + glade_widget_get_is_composite (priv->loaded_widget) ? + _("Class Name:") : _("ID:")); + + if (priv->composite_check) + { + g_signal_handlers_block_by_func (G_OBJECT (priv->composite_check), + G_CALLBACK (widget_composite_toggled), table); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->composite_check), + glade_widget_get_is_composite (priv->loaded_widget)); + g_signal_handlers_unblock_by_func (G_OBJECT (priv->composite_check), + G_CALLBACK (widget_composite_toggled), table); + } +} + +static void +widget_finalized (GladeEditorTable *table, GladeWidget *where_widget_was) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + priv->loaded_widget = NULL; + + glade_editable_load (GLADE_EDITABLE (table), NULL); +} + + +static void +glade_editor_table_load (GladeEditable *editable, GladeWidget *widget) +{ + GladeEditorTable *table = GLADE_EDITOR_TABLE (editable); + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + GladeEditorProperty *property; + GList *list; + + /* Setup the table the first time the widget is loaded */ + if (widget && priv->adaptor == NULL) + { + priv->adaptor = glade_widget_get_adaptor (widget); + + if (priv->type == GLADE_PAGE_GENERAL) + append_name_field (table); + + append_items (table, priv->adaptor, priv->type); + } + + /* abort mission */ + if (priv->loaded_widget == widget) + return; + + if (priv->loaded_widget) + { + g_signal_handlers_disconnect_by_func (G_OBJECT (priv->loaded_widget), + G_CALLBACK (widget_name_changed), + table); + g_signal_handlers_disconnect_by_func (G_OBJECT (priv->loaded_widget), + G_CALLBACK (widget_composite_changed), + table); + + /* The widget could die unexpectedly... */ + g_object_weak_unref (G_OBJECT (priv->loaded_widget), + (GWeakNotify) widget_finalized, table); + } + + priv->loaded_widget = widget; + + BLOCK_NAME_ENTRY_CB (table); + + if (priv->loaded_widget) + { + g_signal_connect (G_OBJECT (priv->loaded_widget), "notify::name", + G_CALLBACK (widget_name_changed), table); + + g_signal_connect (G_OBJECT (priv->loaded_widget), "notify::composite", + G_CALLBACK (widget_composite_changed), table); + + /* The widget could die unexpectedly... */ + g_object_weak_ref (G_OBJECT (priv->loaded_widget), + (GWeakNotify) widget_finalized, table); + + if (priv->composite_check) + { + GObject *object = glade_widget_get_object (priv->loaded_widget); + GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (priv->loaded_widget); + + if (GTK_IS_WIDGET (object) && + glade_widget_get_parent (priv->loaded_widget) == NULL) + gtk_widget_show (priv->composite_check); + else + gtk_widget_hide (priv->composite_check); + + gtk_widget_set_sensitive (priv->composite_check, + !g_str_has_prefix (glade_widget_adaptor_get_name (adaptor), + GLADE_WIDGET_ADAPTOR_INSTANTIABLE_PREFIX)); + } + + if (priv->name_entry) + { + if (glade_widget_has_name (widget)) + gtk_entry_set_text (GTK_ENTRY (priv->name_entry), glade_widget_get_name (widget)); + else + gtk_entry_set_text (GTK_ENTRY (priv->name_entry), ""); + } + + if (priv->name_label) + widget_composite_changed (widget, NULL, table); + } + else if (priv->name_entry) + gtk_entry_set_text (GTK_ENTRY (priv->name_entry), ""); + + UNBLOCK_NAME_ENTRY_CB (table); + + /* Sync up properties, even if widget is NULL */ + for (list = priv->properties; list; list = list->next) + { + property = list->data; + glade_editor_property_load_by_widget (property, widget); + } +} + +static void +glade_editor_table_set_show_name (GladeEditable *editable, gboolean show_name) +{ + GladeEditorTable *table = GLADE_EDITOR_TABLE (editable); + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + + if (priv->show_name != show_name) + { + priv->show_name = show_name; + + if (priv->name_label) + { + gtk_widget_set_visible (priv->name_label, show_name); + gtk_widget_set_visible (priv->name_field, show_name); + } + } +} + +static void +glade_editor_table_editable_init (GladeEditableInterface *iface) +{ + iface->load = glade_editor_table_load; + iface->set_show_name = glade_editor_table_set_show_name; +} + +static void +glade_editor_table_attach (GladeEditorTable * table, + GtkWidget * child, gint pos, gint row) +{ + gtk_grid_attach (GTK_GRID (table), child, pos, row, 1, 1); + + if (pos) + gtk_widget_set_hexpand (child, TRUE); +} + +static gint +property_class_comp (gconstpointer a, gconstpointer b) +{ + GladePropertyDef *ca = (GladePropertyDef *)a, *cb = (GladePropertyDef *)b; + GParamSpec *pa, *pb; + const gchar *name_a, *name_b; + + pa = glade_property_def_get_pspec (ca); + pb = glade_property_def_get_pspec (cb); + + name_a = glade_property_def_id (ca); + name_b = glade_property_def_id (cb); + + /* Special case for the 'name' property, it *always* comes first. */ + if (strcmp (name_a, "name") == 0) + return -1; + else if (strcmp (name_b, "name") == 0) + return 1; + /* Properties of the same class are sorted in the same level */ + else if (pa->owner_type == pb->owner_type) + { + gdouble result = glade_property_def_weight (ca) - glade_property_def_weight (cb); + /* Avoid cast to int */ + if (result < 0.0) + return -1; + else if (result > 0.0) + return 1; + else + return 0; + } + /* Group properties by their class hierarchy */ + else + { + if (g_type_is_a (pa->owner_type, pb->owner_type)) + return (glade_property_def_common (ca) || glade_property_def_get_is_packing (ca)) ? 1 : -1; + else + return (glade_property_def_common (ca) || glade_property_def_get_is_packing (ca)) ? -1 : 1; + } +} + +static GList * +get_sorted_properties (GladeWidgetAdaptor *adaptor, GladeEditorPageType type) +{ + const GList *l, *properties; + GList *list = NULL; + + properties = + (type == GLADE_PAGE_PACKING) ? + glade_widget_adaptor_get_packing_props (adaptor) : + glade_widget_adaptor_get_properties (adaptor); + + for (l = properties; l; l = g_list_next (l)) + { + GladePropertyDef *def = l->data; + + /* Collect properties in our domain, query dialogs are allowed editor + * invisible (or custom-layout) properties, allow adaptors to filter + * out properties from the GladeEditorTable using the "custom-layout" attribute. + */ + if (GLADE_PROPERTY_DEF_IS_TYPE (def, type) && + (type == GLADE_PAGE_QUERY || + (!glade_property_def_custom_layout (def) && + glade_property_def_is_visible (def)))) + { + list = g_list_prepend (list, def); + } + + } + return g_list_sort (list, property_class_comp); +} + +static GladeEditorProperty * +append_item (GladeEditorTable *table, + GladePropertyDef *def, + gboolean from_query_dialog) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + GladeEditorProperty *property; + GtkWidget *label; + + if (!(property = glade_widget_adaptor_create_eprop + (glade_property_def_get_adaptor (def), def, from_query_dialog == FALSE))) + { + g_critical ("Unable to create editor for property '%s' of class '%s'", + glade_property_def_id (def), + glade_widget_adaptor_get_name (glade_property_def_get_adaptor (def))); + return NULL; + } + + gtk_widget_show (GTK_WIDGET (property)); + gtk_widget_show_all (glade_editor_property_get_item_label (property)); + + label = glade_editor_property_get_item_label (property); + gtk_widget_set_hexpand (label, FALSE); + + glade_editor_table_attach (table, label, 0, priv->rows); + glade_editor_table_attach (table, GTK_WIDGET (property), 1, priv->rows); + + priv->rows++; + + return property; +} + +static void +append_items (GladeEditorTable *table, + GladeWidgetAdaptor *adaptor, + GladeEditorPageType type) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + GladeEditorProperty *property; + GladePropertyDef *property_def; + GList *list, *sorted_list; + + sorted_list = get_sorted_properties (adaptor, type); + for (list = sorted_list; list != NULL; list = list->next) + { + property_def = (GladePropertyDef *) list->data; + + property = append_item (table, property_def, type == GLADE_PAGE_QUERY); + priv->properties = g_list_prepend (priv->properties, property); + } + g_list_free (sorted_list); + + priv->properties = g_list_reverse (priv->properties); +} + +static void +append_name_field (GladeEditorTable *table) +{ + GladeEditorTablePrivate *priv = glade_editor_table_get_instance_private (table); + gchar *text = _("The object's unique identifier"); + + /* translators: The unique identifier of an object in the project */ + priv->name_label = gtk_label_new (_("ID:")); + gtk_widget_set_halign (priv->name_label, GTK_ALIGN_START); + gtk_widget_show (priv->name_label); + gtk_widget_set_no_show_all (priv->name_label, TRUE); + + priv->name_field = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 2); + gtk_widget_set_no_show_all (priv->name_field, TRUE); + gtk_widget_show (priv->name_field); + + priv->composite_check = gtk_check_button_new_with_label (_("Composite")); + gtk_widget_set_hexpand (priv->composite_check, FALSE); + gtk_widget_set_tooltip_text (priv->composite_check, _("Whether this widget is a composite template")); + gtk_widget_set_no_show_all (priv->composite_check, TRUE); + + priv->name_entry = gtk_entry_new (); + gtk_widget_show (priv->name_entry); + + gtk_widget_set_tooltip_text (priv->name_label, text); + gtk_widget_set_tooltip_text (priv->name_entry, text); + + g_signal_connect (G_OBJECT (priv->name_entry), "activate", + G_CALLBACK (widget_name_edited), table); + g_signal_connect (G_OBJECT (priv->name_entry), "changed", + G_CALLBACK (widget_name_edited), table); + g_signal_connect (G_OBJECT (priv->composite_check), "toggled", + G_CALLBACK (widget_composite_toggled), table); + + gtk_box_pack_start (GTK_BOX (priv->name_field), priv->name_entry, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (priv->name_field), priv->composite_check, FALSE, FALSE, 0); + + glade_editor_table_attach (table, priv->name_label, 0, priv->rows); + glade_editor_table_attach (table, priv->name_field, 1, priv->rows); + + /* Set initial visibility */ + gtk_widget_set_visible (priv->name_label, priv->show_name); + gtk_widget_set_visible (priv->name_field, priv->show_name); + + priv->rows++; +} + +/** + * glade_editor_table_new: + * @adaptor: A #GladeWidgetAdaptor + * @type: The #GladeEditorPageType + * + * Creates a new #GladeEditorTable. + * + * Returns: a new #GladeEditorTable + * + */ +GtkWidget * +glade_editor_table_new (GladeWidgetAdaptor *adaptor, GladeEditorPageType type) +{ + GladeEditorTable *table; + GladeEditorTablePrivate *priv; + + g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL); + + table = g_object_new (GLADE_TYPE_EDITOR_TABLE, "page-type", type, NULL); + priv = glade_editor_table_get_instance_private (table); + priv->adaptor = adaptor; + + if (priv->type == GLADE_PAGE_GENERAL) + append_name_field (table); + + append_items (table, priv->adaptor, priv->type); + + return (GtkWidget *)table; +} diff --git a/gladeui/glade-editor-table.h b/gladeui/glade-editor-table.h new file mode 100644 index 0000000..d2946b2 --- /dev/null +++ b/gladeui/glade-editor-table.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2008 Tristan Van Berkom. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ +#ifndef _GLADE_EDITOR_BUTTON_H_ +#define _GLADE_EDITOR_BUTTON_H_ + +#include +#include + + +G_BEGIN_DECLS + +#define GLADE_TYPE_EDITOR_TABLE glade_editor_table_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeEditorTable, glade_editor_table, GLADE, EDITOR_TABLE, GtkGrid) + +struct _GladeEditorTableClass +{ + GtkGridClass parent; + + void (* glade_reserved1) (void); + void (* glade_reserved2) (void); + void (* glade_reserved3) (void); + void (* glade_reserved4) (void); +}; + +GtkWidget *glade_editor_table_new (GladeWidgetAdaptor *adaptor, + GladeEditorPageType type); + +G_END_DECLS + +#endif /* _GLADE_EDITOR_TABLE_H_ */ diff --git a/gladeui/glade-editor.c b/gladeui/glade-editor.c new file mode 100644 index 0000000..46f5c5e --- /dev/null +++ b/gladeui/glade-editor.c @@ -0,0 +1,1365 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Chema Celorio + * Tristan Van Berkom + */ + + +#ifdef HAVE_CONFIG_H +#include +#endif + +/** + * SECTION:glade-editor + * @Short_Description: A Widget to edit a #GladeWidget. + * + * This is the Glade Notebook containing all the controls needed to configure a #GladeWidget. + */ + +#include +#include +#include + +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-widget-adaptor.h" +#include "glade-editor.h" +#include "glade-signal-editor.h" +#include "glade-property.h" +#include "glade-property-def.h" +#include "glade-command.h" +#include "glade-debug.h" +#include "glade-marshallers.h" +#include "glade-project.h" +#include "glade-utils.h" +#include "glade-editor-property.h" + +static void glade_editor_switch_page (GtkNotebook *notebook, + GtkWidget *page, + guint page_num, + GladeEditor *editor); + +enum +{ + PROP_0, + PROP_SHOW_INFO, + PROP_WIDGET, + PROP_SHOW_CLASS_FIELD, + PROP_CLASS_FIELD, + PROP_SHOW_BORDER, + PROP_SIGNAL_EDITOR, + N_PROPERTIES +}; + +typedef struct _GladeEditorPrivate +{ + + GtkWidget *notebook; /* The notebook widget */ + + GladeWidget *loaded_widget; /* A handy pointer to the GladeWidget + * that is loaded in the editor. NULL + * if no widgets are selected + */ + + GladeWidgetAdaptor *loaded_adaptor; /* A pointer to the loaded + * GladeWidgetAdaptor. Note that we can + * have a class loaded without a + * loaded_widget. For this reason we + * can't use loaded_widget->adaptor. + * When a widget is selected we load + * this class in the editor first and + * then fill the values of the inputs + * with the GladeProperty items. + * This is usefull for not having + * to redraw/container_add the widgets + * when we switch from widgets of the + * same class + */ + + GtkWidget *page_widget; + GtkWidget *page_packing; + GtkWidget *page_common; + GtkWidget *page_atk; + + GladeSignalEditor *signal_editor; /* The signal editor packed into vbox_signals + */ + + GList *editables; /* A list of GladeEditables. We have a widget + * for each GladeWidgetAdaptor and we only load + * them on demand + */ + + GtkWidget *packing_page; /* Packing pages are dynamically created each + * selection, this pointer is only to free + * the last packing page. + */ + + gboolean loading; /* Use when loading a GladeWidget into the editor + * we set this flag so that we can ignore the + * "changed" signal of the name entry text since + * the name has not really changed, just a new name + * was loaded. + */ + + gulong project_closed_signal_id; /* Unload widget when widget's project closes */ + gulong project_removed_signal_id; /* Unload widget when its removed from the project. */ + gulong widget_warning_id; /* Update when widget changes warning messages. */ + gulong widget_name_id; /* Update class field when widget name changes */ + + GtkWidget *class_field; /* The class header */ + + GtkWidget *warning; /* A pointer to an icon we can show in the class + * field to publish tooltips for class related + * versioning errors. + */ + + GtkWidget *class_icon; /* An image with the current widget's class icon. */ + GtkWidget *class_label; /* A label with the current class label. */ + + gboolean show_class_field; /* Whether or not to show the class field at the top */ +} GladeEditorPrivate; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeEditor, glade_editor, GTK_TYPE_BOX) + +static GParamSpec *properties[N_PROPERTIES]; + +static void +glade_editor_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeEditor *editor = GLADE_EDITOR (object); + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + switch (prop_id) + { + case PROP_SHOW_INFO: + break; + case PROP_WIDGET: + glade_editor_load_widget (editor, + GLADE_WIDGET (g_value_get_object (value))); + break; + case PROP_SHOW_CLASS_FIELD: + if (g_value_get_boolean (value)) + glade_editor_show_class_field (editor); + else + glade_editor_hide_class_field (editor); + break; + case PROP_SHOW_BORDER: + gtk_notebook_set_show_border (GTK_NOTEBOOK (priv->notebook), + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_editor_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeEditor *editor = GLADE_EDITOR (object); + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + switch (prop_id) + { + case PROP_SHOW_INFO: + g_value_set_boolean (value, FALSE); + break; + case PROP_WIDGET: + g_value_set_object (value, priv->loaded_widget); + break; + case PROP_SHOW_CLASS_FIELD: + g_value_set_boolean (value, priv->show_class_field); + break; + case PROP_CLASS_FIELD: + g_value_set_static_string (value, gtk_label_get_label (GTK_LABEL (priv->class_label))); + break; + case PROP_SHOW_BORDER: + g_value_set_boolean (value, gtk_notebook_get_show_border (GTK_NOTEBOOK (priv->notebook))); + break; + case PROP_SIGNAL_EDITOR: + g_value_set_object (value, priv->signal_editor); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_editor_dispose (GObject *object) +{ + GladeEditor *editor = GLADE_EDITOR (object); + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + glade_editor_load_widget (editor, NULL); + + /* Unref all the cached pages */ + g_list_free_full (priv->editables, g_object_unref); + + G_OBJECT_CLASS (glade_editor_parent_class)->dispose (object); +} + +static void +glade_editor_class_init (GladeEditorClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + + object_class->dispose = glade_editor_dispose; + object_class->set_property = glade_editor_set_property; + object_class->get_property = glade_editor_get_property; + + /* Properties */ + properties[PROP_SHOW_INFO] = + g_param_spec_boolean ("show-info", + _("Show info"), + _("Whether to show an informational " + "button for the loaded widget"), + FALSE, + G_PARAM_READABLE | G_PARAM_DEPRECATED); + + properties[PROP_WIDGET] = + g_param_spec_object ("widget", + _("Widget"), + _("The currently loaded widget in this editor"), + GLADE_TYPE_WIDGET, + G_PARAM_READWRITE); + + properties[PROP_SHOW_CLASS_FIELD] = + g_param_spec_boolean ("show-class-field", + _("Show Class Field"), + _("Whether to show the class field at the top"), + TRUE, + G_PARAM_READWRITE); + + properties[PROP_CLASS_FIELD] = + g_param_spec_string ("class-field", + _("Class Field"), + _("The class field string"), + NULL, + G_PARAM_READABLE); + + properties[PROP_SHOW_BORDER] = + g_param_spec_boolean ("show-boder", + _("Show Border"), + _("Whether the border should be shown"), + TRUE, + G_PARAM_READWRITE); + + properties[PROP_SIGNAL_EDITOR] = + g_param_spec_object ("signal-editor", + _("Signal Editor"), + _("The signal editor used to edit signals"), + GLADE_TYPE_SIGNAL_EDITOR, + G_PARAM_READABLE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + /* Bind to template */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-editor.ui"); + + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, notebook); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_widget); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_packing); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_common); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, page_atk); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, class_field); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, class_icon); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, class_label); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, warning); + gtk_widget_class_bind_template_child_private (widget_class, GladeEditor, signal_editor); + + gtk_widget_class_bind_template_callback (widget_class, glade_editor_switch_page); +} + +static void +glade_editor_update_class_warning_cb (GladeWidget *widget, + GParamSpec *pspec, + GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + if (glade_widget_support_warning (widget)) + gtk_widget_show (priv->warning); + else + gtk_widget_hide (priv->warning); + + gtk_widget_set_tooltip_text (priv->warning, glade_widget_support_warning (widget)); +} + + +static void +glade_editor_update_class_field (GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + if (priv->loaded_widget) + { + GladeWidget *widget = priv->loaded_widget; + gchar *text; + + gtk_image_set_from_icon_name (GTK_IMAGE (priv->class_icon), + glade_widget_adaptor_get_icon_name (priv->loaded_adaptor), + GTK_ICON_SIZE_BUTTON); + gtk_widget_show (priv->class_icon); + + if (glade_widget_has_name (widget)) + { + /* translators: %s(Class Title) Properties - %s (ClassName) [%s(WidgetName)] + * example: Window Properties - GtkWindow [window1] + */ + text = g_strdup_printf (_("%s Properties - %s [%s]"), + glade_widget_adaptor_get_title (priv->loaded_adaptor), + glade_widget_adaptor_get_display_name (priv->loaded_adaptor), + glade_widget_get_display_name (widget)); + } + else + { + /* translators: %s(Class Title) Properties - %s (ClassName) + * example: Window Properties - GtkWindow + */ + text = g_strdup_printf (_("%s Properties - %s"), + glade_widget_adaptor_get_title (priv->loaded_adaptor), + glade_widget_adaptor_get_display_name (priv->loaded_adaptor)); + } + + gtk_label_set_text (GTK_LABEL (priv->class_label), text); + g_free (text); + + glade_editor_update_class_warning_cb (priv->loaded_widget, NULL, editor); + } + else + { + gtk_widget_hide (priv->class_icon); + gtk_widget_hide (priv->warning); + gtk_label_set_text (GTK_LABEL (priv->class_label), _("Properties")); + } + + g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_CLASS_FIELD]); +} + +static void +glade_editor_update_widget_name_cb (GladeWidget *widget, + GParamSpec *pspec, + GladeEditor *editor) +{ + glade_editor_update_class_field (editor); +} + +static void +glade_editor_switch_page (GtkNotebook *notebook, + GtkWidget *page, + guint page_num, + GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + gtk_widget_hide (priv->page_widget); + gtk_widget_hide (priv->page_packing); + gtk_widget_hide (priv->page_common); + gtk_widget_hide (priv->page_atk); + + switch (page_num) + { + case 0: + gtk_widget_show (priv->page_widget); + break; + case 1: + gtk_widget_show (priv->page_packing); + break; + case 2: + gtk_widget_show (priv->page_common); + break; + case 4: + gtk_widget_show (priv->page_atk); + break; + } +} + +static void +glade_editor_init (GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + gint icon_height; + + priv->show_class_field = TRUE; + + gtk_widget_init_template (GTK_WIDGET (editor)); + + gtk_icon_size_lookup (GTK_ICON_SIZE_BUTTON, NULL, &icon_height); + gtk_widget_set_size_request (priv->class_label, -1, icon_height + 2); + + glade_editor_update_class_field (editor); +} + +static GtkWidget * +glade_editor_get_editable_by_adaptor (GladeEditor *editor, + GladeWidgetAdaptor *adaptor, + GladeEditorPageType type) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + GtkWidget *editable; + GList *list; + + g_return_val_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor), NULL); + + for (list = priv->editables; list; list = list->next) + { + editable = list->data; + if (type != + GPOINTER_TO_INT (g_object_get_data + (G_OBJECT (editable), "glade-editor-page-type"))) + continue; + if (g_object_get_data (G_OBJECT (editable), "glade-widget-adaptor") == + adaptor) + return editable; + } + + editable = (GtkWidget *) glade_widget_adaptor_create_editable (adaptor, type); + g_return_val_if_fail (editable != NULL, NULL); + + g_object_set_data (G_OBJECT (editable), "glade-editor-page-type", + GINT_TO_POINTER (type)); + g_object_set_data (G_OBJECT (editable), "glade-widget-adaptor", adaptor); + + if (type != GLADE_PAGE_PACKING) + { + priv->editables = g_list_prepend (priv->editables, editable); + g_object_ref_sink (editable); + } + + return editable; +} + +static void +hide_or_remove_visible_child (GtkContainer *container, gboolean remove) +{ + GList *l, *children = gtk_container_get_children (container); + GtkWidget *widget; + + for (l = children; l; l = l->next) + { + widget = l->data; + + if (gtk_widget_get_visible (widget)) + { + gtk_widget_hide (widget); + + if (remove) + gtk_container_remove (container, widget); + + break; + } + } + g_list_free (children); +} + +static GtkWidget * +glade_editor_load_editable_in_page (GladeEditor *editor, + GladeWidgetAdaptor *adaptor, + GladeEditorPageType type) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + GtkContainer *container = NULL; + GtkWidget *scrolled_window, *editable; + GtkAdjustment *adj; + + /* Remove the old table that was in this container */ + switch (type) + { + case GLADE_PAGE_GENERAL: + container = GTK_CONTAINER (priv->page_widget); + break; + case GLADE_PAGE_COMMON: + container = GTK_CONTAINER (priv->page_common); + break; + case GLADE_PAGE_PACKING: + container = GTK_CONTAINER (priv->page_packing); + break; + case GLADE_PAGE_ATK: + container = GTK_CONTAINER (priv->page_atk); + break; + case GLADE_PAGE_QUERY: + default: + g_critical ("Unreachable code reached !"); + break; + } + + /* Hide the editable (this will destroy on packing pages) */ + hide_or_remove_visible_child (container, type == GLADE_PAGE_PACKING); + + if (!adaptor) + return NULL; + + if ((editable = + glade_editor_get_editable_by_adaptor (editor, adaptor, type)) == NULL) + return NULL; + + /* Attach the new page */ + if (!gtk_widget_get_parent (editable)) + gtk_container_add (GTK_CONTAINER (container), editable); + gtk_widget_show (editable); + + if ((scrolled_window = + gtk_widget_get_ancestor (GTK_WIDGET (container), + GTK_TYPE_SCROLLED_WINDOW)) != NULL) + { + adj = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled_window)); + gtk_container_set_focus_vadjustment (GTK_CONTAINER (editable), adj); + + adj = gtk_scrolled_window_get_hadjustment (GTK_SCROLLED_WINDOW (scrolled_window)); + gtk_container_set_focus_hadjustment (GTK_CONTAINER (editable), adj); + } + + return editable; +} + +static void +glade_editor_load_widget_class (GladeEditor *editor, + GladeWidgetAdaptor *adaptor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + glade_editor_load_editable_in_page (editor, adaptor, GLADE_PAGE_GENERAL); + glade_editor_load_editable_in_page (editor, adaptor, GLADE_PAGE_COMMON); + glade_editor_load_editable_in_page (editor, adaptor, GLADE_PAGE_ATK); + glade_editor_load_editable_in_page (editor, NULL, GLADE_PAGE_PACKING); + + priv->loaded_adaptor = adaptor; +} + +static void +glade_editor_close_cb (GladeProject *project, GladeEditor *editor) +{ + /* project we are viewing was closed, + * detouch from editor. + */ + glade_editor_load_widget (editor, NULL); +} + +static void +glade_editor_removed_cb (GladeProject *project, + GladeWidget *widget, + GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + /* Widget we were viewing was removed from project, + * detouch from editor. + */ + if (widget == priv->loaded_widget) + glade_editor_load_widget (editor, NULL); + +} + + +static void +glade_editor_load_editable (GladeEditor *editor, + GladeWidget *widget, + GladeEditorPageType type) +{ + GtkWidget *editable; + GladeWidget *parent = glade_widget_get_parent (widget); + + /* Use the parenting adaptor for packing pages... so deffer creating the widgets + * until load time. + */ + if (type == GLADE_PAGE_PACKING) + { + GladeWidgetAdaptor *adaptor; + + if (!parent) + return; + + adaptor = glade_widget_get_adaptor (parent); + editable = + glade_editor_load_editable_in_page (editor, adaptor, + GLADE_PAGE_PACKING); + } + else + editable = + glade_editor_get_editable_by_adaptor (editor, + glade_widget_get_adaptor (widget), + type); + + g_assert (editable); + + if (!widget) + gtk_widget_hide (editable); + + glade_editable_load (GLADE_EDITABLE (editable), widget); + + if (widget) + gtk_widget_show (editable); +} + +static void +clear_editables (GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + GladeEditable *editable; + GList *l; + + for (l = priv->editables; l; l = l->next) + { + editable = l->data; + glade_editable_load (editable, NULL); + } +} + +static void +glade_editor_load_widget_real (GladeEditor *editor, GladeWidget *widget) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + GladeWidgetAdaptor *adaptor; + GladeProject *project; + + /* Disconnect from last widget */ + if (priv->loaded_widget != NULL) + { + /* better pay a small price now and avoid unseen editables + * waking up on project metadata changes. + */ + clear_editables (editor); + + project = glade_widget_get_project (priv->loaded_widget); + g_signal_handler_disconnect (G_OBJECT (project), + priv->project_closed_signal_id); + g_signal_handler_disconnect (G_OBJECT (project), + priv->project_removed_signal_id); + g_signal_handler_disconnect (G_OBJECT (priv->loaded_widget), + priv->widget_warning_id); + g_signal_handler_disconnect (G_OBJECT (priv->loaded_widget), + priv->widget_name_id); + } + + /* Load the GladeWidgetAdaptor */ + adaptor = widget ? glade_widget_get_adaptor (widget) : NULL; + if (priv->loaded_adaptor != adaptor || adaptor == NULL) + glade_editor_load_widget_class (editor, adaptor); + + glade_signal_editor_load_widget (priv->signal_editor, widget); + + /* we are just clearing, we are done */ + if (widget == NULL) + { + priv->loaded_widget = NULL; + + /* Clear class header */ + glade_editor_update_class_field (editor); + + g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_WIDGET]); + + return; + } + + priv->loading = TRUE; + + /* Load each GladeEditorProperty from 'widget' */ + glade_editor_load_editable (editor, widget, GLADE_PAGE_GENERAL); + glade_editor_load_editable (editor, widget, GLADE_PAGE_COMMON); + glade_editor_load_editable (editor, widget, GLADE_PAGE_ATK); + glade_editor_load_editable (editor, widget, GLADE_PAGE_PACKING); + + priv->loaded_widget = widget; + priv->loading = FALSE; + + /* Update class header */ + glade_editor_update_class_field (editor); + + /* Connect to new widget */ + project = glade_widget_get_project (priv->loaded_widget); + priv->project_closed_signal_id = + g_signal_connect (G_OBJECT (project), "close", + G_CALLBACK (glade_editor_close_cb), editor); + priv->project_removed_signal_id = + g_signal_connect (G_OBJECT (project), "remove-widget", + G_CALLBACK (glade_editor_removed_cb), editor); + priv->widget_warning_id = + g_signal_connect (G_OBJECT (widget), "notify::support-warning", + G_CALLBACK (glade_editor_update_class_warning_cb), + editor); + priv->widget_name_id = + g_signal_connect (G_OBJECT (widget), "notify::name", + G_CALLBACK (glade_editor_update_widget_name_cb), + editor); + + g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_WIDGET]); +} + + +/** + * glade_editor_new: + * + * Returns: a new #GladeEditor + */ +GladeEditor * +glade_editor_new (void) +{ + GladeEditor *editor; + + editor = g_object_new (GLADE_TYPE_EDITOR, "spacing", 6, NULL); + + return editor; +} + +/** + * glade_editor_load_widget: + * @editor: a #GladeEditor + * @widget: a #GladeWidget + * + * Load @widget into @editor. If @widget is %NULL, clear the editor. + */ +void +glade_editor_load_widget (GladeEditor *editor, GladeWidget *widget) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + g_return_if_fail (GLADE_IS_EDITOR (editor)); + g_return_if_fail (widget == NULL || GLADE_IS_WIDGET (widget)); + + if (priv->loaded_widget == widget) + return; + + glade_editor_load_widget_real (editor, widget); +} + +static void +query_dialog_style_set_cb (GtkWidget *dialog, + GtkStyle *previous_style, + gpointer user_data) +{ + GtkWidget *content_area, *action_area; + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_container_set_border_width (GTK_CONTAINER (content_area), 12); + gtk_box_set_spacing (GTK_BOX (content_area), 12); + action_area = gtk_dialog_get_action_area (GTK_DIALOG (dialog)); + gtk_container_set_border_width (GTK_CONTAINER (action_area), 0); + gtk_box_set_spacing (GTK_BOX (action_area), 6); +} + +static gboolean +query_dialog_delete_event_cb (GtkDialog *dialog, + GdkEvent *event, + gpointer user_data) +{ + gtk_dialog_response (dialog, GTK_RESPONSE_CANCEL); + return TRUE; +} + +gboolean +glade_editor_query_dialog (GladeWidget *widget) +{ + GladeWidgetAdaptor *adaptor; + GtkWidget *dialog, *editable, *content_area; + GtkWidget *create; + gchar *title; + gint answer; + gboolean retval = TRUE; + + g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE); + + adaptor = glade_widget_get_adaptor (widget); + + title = g_strdup_printf (_("Create a %s"), glade_widget_adaptor_get_display_name (adaptor)); + dialog = gtk_dialog_new_with_buttons (title, NULL, + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + _("_Cancel"), GTK_RESPONSE_CANCEL, + NULL); + g_free (title); + + create = gtk_button_new_with_mnemonic (_("Crea_te")); + gtk_widget_show (create); + gtk_widget_set_can_default (create, TRUE); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), create, GTK_RESPONSE_OK); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK); + + editable = (GtkWidget *) glade_widget_adaptor_create_editable (adaptor, GLADE_PAGE_QUERY); + gtk_widget_show (editable); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + gtk_box_pack_start (GTK_BOX (content_area), editable, FALSE, FALSE, 6); + + glade_editable_load (GLADE_EDITABLE (editable), widget); + + g_signal_connect (dialog, "style-set", + G_CALLBACK (query_dialog_style_set_cb), NULL); + + g_signal_connect (dialog, "delete-event", + G_CALLBACK (query_dialog_delete_event_cb), NULL); + + answer = gtk_dialog_run (GTK_DIALOG (dialog)); + + /* + * If user cancel's we cancel the whole "create operation" by + * return FALSE. glade_widget_new() will see the FALSE, and + * take care of canceling the "create" operation. + */ + if (answer == GTK_RESPONSE_CANCEL) + retval = FALSE; + + gtk_widget_destroy (dialog); + return retval; +} + +enum +{ + COLUMN_ENABLED = 0, + COLUMN_PROP_NAME, + COLUMN_PROPERTY, + COLUMN_WEIGHT, + COLUMN_CHILD, + COLUMN_DEFAULT, + COLUMN_NDEFAULT, + COLUMN_DEFSTRING, + NUM_COLUMNS +}; + + +static void +glade_editor_reset_toggled (GtkCellRendererToggle *cell, + gchar *path_str, + GtkTreeModel *model) +{ + GtkTreePath *path = gtk_tree_path_new_from_string (path_str); + GtkTreeIter iter; + gboolean enabled; + + /* get toggled iter */ + gtk_tree_model_get_iter (model, &iter, path); + gtk_tree_model_get (model, &iter, COLUMN_ENABLED, &enabled, -1); + gtk_tree_store_set (GTK_TREE_STORE (model), &iter, + COLUMN_ENABLED, !enabled, -1); + gtk_tree_path_free (path); +} + +static GtkWidget * +glade_editor_reset_view (void) +{ + GtkWidget *view_widget; + GtkTreeModel *model; + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + model = (GtkTreeModel *) gtk_tree_store_new (NUM_COLUMNS, G_TYPE_BOOLEAN, /* Enabled value */ + G_TYPE_STRING, /* Property name */ + GLADE_TYPE_PROPERTY, /* The property */ + G_TYPE_INT, /* Parent node ? */ + G_TYPE_BOOLEAN, /* Child node ? */ + G_TYPE_BOOLEAN, /* Has default value */ + G_TYPE_BOOLEAN, /* Doesn't have default */ + G_TYPE_STRING); /* Default string */ + + view_widget = gtk_tree_view_new_with_model (model); + g_object_set (G_OBJECT (view_widget), "enable-search", FALSE, NULL); + + /********************* fake invisible column *********************/ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "editable", FALSE, "visible", FALSE, NULL); + + column = gtk_tree_view_column_new_with_attributes (NULL, renderer, NULL); + gtk_tree_view_append_column (GTK_TREE_VIEW (view_widget), column); + + gtk_tree_view_column_set_visible (column, FALSE); + gtk_tree_view_set_expander_column (GTK_TREE_VIEW (view_widget), column); + + /************************ enabled column ************************/ + renderer = gtk_cell_renderer_toggle_new (); + g_object_set (G_OBJECT (renderer), + "mode", GTK_CELL_RENDERER_MODE_ACTIVATABLE, + "activatable", TRUE, NULL); + g_signal_connect (renderer, "toggled", + G_CALLBACK (glade_editor_reset_toggled), model); + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW (view_widget), COLUMN_ENABLED, + _("Reset"), renderer, + "sensitive", COLUMN_NDEFAULT, + "activatable", COLUMN_NDEFAULT, + "active", COLUMN_ENABLED, "visible", COLUMN_CHILD, NULL); + + /********************* property name column *********************/ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "editable", FALSE, NULL); + + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW (view_widget), COLUMN_PROP_NAME, + _("Property"), renderer, + "text", COLUMN_PROP_NAME, "weight", COLUMN_WEIGHT, NULL); + + /******************* default indicator column *******************/ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "editable", FALSE, + "style", PANGO_STYLE_ITALIC, "foreground", "Gray", NULL); + + gtk_tree_view_insert_column_with_attributes + (GTK_TREE_VIEW (view_widget), COLUMN_DEFSTRING, + NULL, renderer, + "text", COLUMN_DEFSTRING, "visible", COLUMN_DEFAULT, NULL); + + return view_widget; +} + +static void +glade_editor_populate_reset_view (GladeWidget *widget, GtkTreeView *tree_view) +{ + GtkTreeStore *model = GTK_TREE_STORE (gtk_tree_view_get_model (tree_view)); + GtkTreeIter property_iter, general_iter, common_iter, atk_iter, *iter; + GList *list; + GladeProperty *property; + GladePropertyDef *pdef; + gboolean def; + + g_return_if_fail (widget != NULL); + + gtk_tree_store_append (model, &general_iter, NULL); + gtk_tree_store_set (model, &general_iter, + COLUMN_PROP_NAME, _("General"), + COLUMN_PROPERTY, NULL, + COLUMN_WEIGHT, PANGO_WEIGHT_BOLD, + COLUMN_CHILD, FALSE, + COLUMN_DEFAULT, FALSE, COLUMN_NDEFAULT, FALSE, -1); + + gtk_tree_store_append (model, &common_iter, NULL); + gtk_tree_store_set (model, &common_iter, + COLUMN_PROP_NAME, _("Common"), + COLUMN_PROPERTY, NULL, + COLUMN_WEIGHT, PANGO_WEIGHT_BOLD, + COLUMN_CHILD, FALSE, + COLUMN_DEFAULT, FALSE, COLUMN_NDEFAULT, FALSE, -1); + + gtk_tree_store_append (model, &atk_iter, NULL); + gtk_tree_store_set (model, &atk_iter, + COLUMN_PROP_NAME, _("Accessibility"), + COLUMN_PROPERTY, NULL, + COLUMN_WEIGHT, PANGO_WEIGHT_BOLD, + COLUMN_CHILD, FALSE, + COLUMN_DEFAULT, FALSE, COLUMN_NDEFAULT, FALSE, -1); + + /* General & Common */ + for (list = glade_widget_get_properties (widget); list; list = list->next) + { + property = list->data; + pdef = glade_property_get_def (property); + + if (glade_property_def_is_visible (pdef) == FALSE) + continue; + + if (glade_property_def_atk (pdef)) + iter = &atk_iter; + else if (glade_property_def_common (pdef)) + iter = &common_iter; + else + iter = &general_iter; + + def = glade_property_default (property); + + gtk_tree_store_append (model, &property_iter, iter); + gtk_tree_store_set (model, &property_iter, + COLUMN_ENABLED, !def, + COLUMN_PROP_NAME, glade_property_def_get_name (pdef), + COLUMN_PROPERTY, property, + COLUMN_WEIGHT, PANGO_WEIGHT_NORMAL, + COLUMN_CHILD, TRUE, + COLUMN_DEFAULT, def, + COLUMN_NDEFAULT, !def, + COLUMN_DEFSTRING, _("(default)"), -1); + } +} + +static gboolean +glade_editor_reset_selection_changed_cb (GtkTreeSelection *selection, + GtkTextView *desc_view) +{ + GtkTreeIter iter; + GladeProperty *property = NULL; + GtkTreeModel *model = NULL; + GtkTextBuffer *text_buffer; + GladePropertyDef *pdef = NULL; + + const gchar *message = + _("Select the properties that you want to reset to their default values"); + + /* Find selected data and show property blurb here */ + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + text_buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (desc_view)); + gtk_tree_model_get (model, &iter, COLUMN_PROPERTY, &property, -1); + + if (property) + pdef = glade_property_get_def (property); + + gtk_text_buffer_set_text (text_buffer, + pdef ? glade_property_def_get_tooltip (pdef) : message, + -1); + if (property) + g_object_unref (G_OBJECT (property)); + } + return TRUE; +} + +static gboolean +glade_editor_reset_foreach_selection (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + gboolean select = GPOINTER_TO_INT (data); + gboolean def; + + gtk_tree_model_get (model, iter, COLUMN_DEFAULT, &def, -1); + /* Dont modify rows that are already default */ + if (def == FALSE) + gtk_tree_store_set (GTK_TREE_STORE (model), iter, + COLUMN_ENABLED, select, -1); + return FALSE; +} + + +static void +glade_editor_reset_select_all_clicked (GtkButton *button, + GtkTreeView *tree_view) +{ + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + gtk_tree_model_foreach (model, + glade_editor_reset_foreach_selection, + GINT_TO_POINTER (TRUE)); +} + +static void +glade_editor_reset_unselect_all_clicked (GtkButton *button, + GtkTreeView *tree_view) +{ + GtkTreeModel *model = gtk_tree_view_get_model (tree_view); + gtk_tree_model_foreach (model, + glade_editor_reset_foreach_selection, + GINT_TO_POINTER (FALSE)); +} + +static gboolean +glade_editor_reset_accumulate_selected_props (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data) +{ + GList **accum = data; + GladeProperty *property; + gboolean enabled, def; + + gtk_tree_model_get (model, iter, + COLUMN_PROPERTY, &property, + COLUMN_ENABLED, &enabled, COLUMN_DEFAULT, &def, -1); + + if (property && enabled && !def) + *accum = g_list_prepend (*accum, property); + + + if (property) + g_object_unref (G_OBJECT (property)); + + return FALSE; +} + +static GList * +glade_editor_reset_get_selected_props (GtkTreeModel *model) +{ + GList *ret = NULL; + + gtk_tree_model_foreach (model, + glade_editor_reset_accumulate_selected_props, + &ret); + + return ret; +} + +static void +glade_editor_reset_properties (GList *props) +{ + GList *list, *sdata_list = NULL; + GladeCommandSetPropData *sdata; + GladeProperty *prop; + GladeWidget *widget; + GladeProject *project = NULL; + + for (list = props; list; list = list->next) + { + prop = list->data; + widget = glade_property_get_widget (prop); + project = glade_widget_get_project (widget); + + sdata = g_new (GladeCommandSetPropData, 1); + sdata->property = prop; + + sdata->old_value = g_new0 (GValue, 1); + sdata->new_value = g_new0 (GValue, 1); + + glade_property_get_value (prop, sdata->old_value); + glade_property_get_default (prop, sdata->new_value); + + sdata_list = g_list_prepend (sdata_list, sdata); + } + + if (project) + /* GladeCommand takes ownership of allocated list, ugly but practical */ + glade_command_set_properties_list (project, sdata_list); + +} + +void +glade_editor_reset_dialog_run (GtkWidget *parent, GladeWidget *gwidget) +{ + GtkTreeSelection *selection; + GtkWidget *dialog; + GtkWidget *vbox, *hbox, *label, *sw, *button; + GtkWidget *tree_view, *description_view; + gint res; + GList *list; + + dialog = gtk_dialog_new_with_buttons (_("Reset Widget Properties"), + parent ? GTK_WINDOW (parent) : NULL, + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + _("_Cancel"), GTK_RESPONSE_CANCEL, + _("_OK"), GTK_RESPONSE_OK, NULL); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_show (vbox); + + gtk_container_set_border_width (GTK_CONTAINER (vbox), 6); + + gtk_box_pack_start (GTK_BOX + (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), vbox, + TRUE, TRUE, 0); + + /* Checklist */ + label = gtk_label_new_with_mnemonic (_("_Properties:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_set_size_request (sw, 400, 200); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + + tree_view = glade_editor_reset_view (); + if (gwidget) + glade_editor_populate_reset_view (gwidget, GTK_TREE_VIEW (tree_view)); + gtk_tree_view_expand_all (GTK_TREE_VIEW (tree_view)); + + gtk_widget_show (tree_view); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), tree_view); + gtk_container_add (GTK_CONTAINER (sw), tree_view); + + /* Select all / Unselect all */ + hbox = gtk_button_box_new (GTK_ORIENTATION_HORIZONTAL); + gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END); + gtk_widget_show (hbox); + gtk_box_pack_start (GTK_BOX (vbox), hbox, TRUE, TRUE, 0); + + button = gtk_button_new_with_mnemonic (_("_Select All")); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (button), 6); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (glade_editor_reset_select_all_clicked), + tree_view); + + button = gtk_button_new_with_mnemonic (_("_Unselect All")); + gtk_widget_show (button); + gtk_box_pack_start (GTK_BOX (hbox), button, TRUE, TRUE, 0); + gtk_container_set_border_width (GTK_CONTAINER (button), 6); + g_signal_connect (G_OBJECT (button), "clicked", + G_CALLBACK (glade_editor_reset_unselect_all_clicked), + tree_view); + + + /* Description */ + label = gtk_label_new_with_mnemonic (_("Property _Description:")); + gtk_widget_show (label); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_widget_set_size_request (sw, 400, 80); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + + description_view = gtk_text_view_new (); + gtk_text_view_set_editable (GTK_TEXT_VIEW (description_view), FALSE); + gtk_text_view_set_wrap_mode (GTK_TEXT_VIEW (description_view), GTK_WRAP_WORD); + + gtk_widget_show (description_view); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), description_view); + gtk_container_add (GTK_CONTAINER (sw), description_view); + + /* Update description */ + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree_view)); + g_signal_connect (G_OBJECT (selection), "changed", + G_CALLBACK (glade_editor_reset_selection_changed_cb), + description_view); + + + + /* Run the dialog */ + res = gtk_dialog_run (GTK_DIALOG (dialog)); + if (res == GTK_RESPONSE_OK) + { + + /* get all selected properties and reset properties through glade_command */ + if ((list = glade_editor_reset_get_selected_props + (gtk_tree_view_get_model (GTK_TREE_VIEW (tree_view)))) != NULL) + { + glade_editor_reset_properties (list); + g_list_free (list); + } + } + gtk_widget_destroy (dialog); +} + +void +glade_editor_show_info (GladeEditor *editor) +{ + g_warning ("%s function is deprecated and does nothing", __func__); +} + +void +glade_editor_hide_info (GladeEditor *editor) +{ + g_warning ("%s function is deprecated and does nothing", __func__); +} + +void +glade_editor_show_class_field (GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + g_return_if_fail (GLADE_IS_EDITOR (editor)); + + if (priv->show_class_field != TRUE) + { + priv->show_class_field = TRUE; + gtk_widget_show (priv->class_field); + + g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_SHOW_CLASS_FIELD]); + } +} + +void +glade_editor_hide_class_field (GladeEditor *editor) +{ + GladeEditorPrivate *priv = glade_editor_get_instance_private (editor); + + g_return_if_fail (GLADE_IS_EDITOR (editor)); + + if (priv->show_class_field != FALSE) + { + priv->show_class_field = FALSE; + gtk_widget_hide (priv->class_field); + + g_object_notify_by_pspec (G_OBJECT (editor), properties[PROP_SHOW_CLASS_FIELD]); + } +} + +static void +editor_widget_name_changed (GladeWidget *widget, + GParamSpec *pspec, + GtkWindow *window) +{ + gchar *title, *prj_name; + + prj_name = glade_project_get_name (glade_widget_get_project (widget)); + /* Translators: first %s is the project name, second is a widget name */ + title = g_strdup_printf (_("%s - %s Properties"), prj_name, + glade_widget_get_display_name (widget)); + gtk_window_set_title (window, title); + g_free (title); + g_free (prj_name); +} + +/** + * glade_editor_dialog_for_widget: + * @widget: a #GladeWidget + * + * This convenience function creates a new dialog window to edit @widget + * specifically. + * + * Returns: (transfer full): the newly created dialog window + */ +GtkWidget * +glade_editor_dialog_for_widget (GladeWidget *widget) +{ + GtkWidget *window, *editor; + + g_return_val_if_fail (GLADE_IS_WIDGET (widget), NULL); + + /* Window */ + window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_type_hint (GTK_WINDOW (window), GDK_WINDOW_TYPE_HINT_UTILITY); + + /* Keep the title up to date */ + editor_widget_name_changed (widget, NULL, GTK_WINDOW (window)); + g_signal_connect_object (G_OBJECT (widget), "notify::name", + G_CALLBACK (editor_widget_name_changed), window, 0); + + if (glade_app_get_accel_group ()) + { + gtk_window_add_accel_group (GTK_WINDOW (window), + glade_app_get_accel_group ()); + g_signal_connect (G_OBJECT (window), "key-press-event", + G_CALLBACK (glade_utils_hijack_key_press), NULL); + } + + editor = (GtkWidget *)glade_editor_new (); + glade_editor_load_widget (GLADE_EDITOR (editor), widget); + + g_signal_connect_swapped (G_OBJECT (editor), "notify::widget", + G_CALLBACK (gtk_widget_destroy), window); + + gtk_container_set_border_width (GTK_CONTAINER (editor), 6); + gtk_container_add (GTK_CONTAINER (window), GTK_WIDGET (editor)); + + gtk_window_set_default_size (GTK_WINDOW (window), 400, 480); + + gtk_widget_show (editor); + + return window; +} diff --git a/gladeui/glade-editor.h b/gladeui/glade-editor.h new file mode 100644 index 0000000..968fc8a --- /dev/null +++ b/gladeui/glade-editor.h @@ -0,0 +1,42 @@ +#ifndef __GLADE_EDITOR_H__ +#define __GLADE_EDITOR_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_EDITOR glade_editor_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeEditor, glade_editor, GLADE, EDITOR, GtkBox) + +/* The GladeEditor is a window that is used to display and modify widget + * properties. The glade editor contains the details of the selected + * widget for the selected project + */ + +struct _GladeEditorClass +{ + GtkBoxClass parent_class; + + gpointer padding[4]; +}; + +GladeEditor *glade_editor_new (void); +void glade_editor_load_widget (GladeEditor *editor, + GladeWidget *widget); +G_DEPRECATED +void glade_editor_show_info (GladeEditor *editor); +G_DEPRECATED +void glade_editor_hide_info (GladeEditor *editor); + +void glade_editor_show_class_field (GladeEditor *editor); +void glade_editor_hide_class_field (GladeEditor *editor); + +gboolean glade_editor_query_dialog (GladeWidget *widget); +GtkWidget *glade_editor_dialog_for_widget (GladeWidget *widget); +void glade_editor_reset_dialog_run (GtkWidget *parent, + GladeWidget *gwidget); + +G_END_DECLS + +#endif /* __GLADE_EDITOR_H__ */ diff --git a/gladeui/glade-editor.ui b/gladeui/glade-editor.ui new file mode 100644 index 0000000..f48eb53 --- /dev/null +++ b/gladeui/glade-editor.ui @@ -0,0 +1,318 @@ + + + + + + + diff --git a/gladeui/glade-id-allocator.c b/gladeui/glade-id-allocator.c new file mode 100644 index 0000000..dacf8bf --- /dev/null +++ b/gladeui/glade-id-allocator.c @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2004 Owen Taylor + * + * Authors: + * Owen Taylor + * + * Modified by the Glade developers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include "config.h" +#include "glade-id-allocator.h" + +#include +#include + +#define INITIAL_WORDS 4 + +struct _GladeIDAllocator +{ + guint n_words; + guint32 *data; +}; + +/** + * glade_id_allocator_new: (skip) + * + * Returns: a new #GladeIDAllocator + */ +GladeIDAllocator * +glade_id_allocator_new (void) +{ + GladeIDAllocator *allocator = g_slice_new (GladeIDAllocator); + + allocator->n_words = INITIAL_WORDS; + allocator->data = g_new (guint32, INITIAL_WORDS); + + memset (allocator->data, 0xff, INITIAL_WORDS * sizeof (guint32)); + + return allocator; +} + +/** + * glade_id_allocator_destroy: + * @allocator: a #GladeIDAllocator + * + * Frees @allocator and its associated memory + */ +void +glade_id_allocator_destroy (GladeIDAllocator *allocator) +{ + g_return_if_fail (allocator != NULL); + + g_free (allocator->data); + g_slice_free (GladeIDAllocator, allocator); +} + +static inline gint +first_set_bit (guint32 word) +{ + static const char table[16] = { + 4, 0, 1, 0, + 2, 0, 1, 0, + 3, 0, 1, 0, + 2, 0, 1, 0 + }; + + gint result = 0; + + if ((word & 0xffff) == 0) + { + word >>= 16; + result += 16; + } + + if ((word & 0xff) == 0) + { + word >>= 8; + result += 8; + } + + if ((word & 0xf) == 0) + { + word >>= 4; + result += 4; + } + + return result + table[word & 0xf]; +} + +/** + * glade_id_allocator_allocate: + * @allocator: a #GladeIDAllocator + * + * TODO: write me + * Returns: the id + */ +guint +glade_id_allocator_allocate (GladeIDAllocator *allocator) +{ + guint i; + + g_return_val_if_fail (allocator != NULL, 0); + + for (i = 0; i < allocator->n_words; i++) + { + if (allocator->data[i] != 0) + { + gint free_bit = first_set_bit (allocator->data[i]); + allocator->data[i] &= ~(1 << free_bit); + + return 32 * i + free_bit + 1; + } + } + + { + guint n_words = allocator->n_words; + + allocator->data = g_renew (guint32, allocator->data, n_words * 2); + memset (&allocator->data[n_words], 0xff, n_words * sizeof (guint32)); + allocator->n_words = n_words * 2; + + allocator->data[n_words] = 0xffffffff - 1; + + return 32 * n_words + 1; + } +} + +/** + * glade_id_allocator_release: + * @allocator: a #GladeIDAllocator + * @id: the id given by glade_id_allocator_allocate() + * + * TODO: write me + */ +void +glade_id_allocator_release (GladeIDAllocator *allocator, guint id) +{ + guint word_idx; + + g_return_if_fail (allocator != NULL); + + /* Allocated ids start with 1 */ + if (id > 0) + { + id = id - 1; + word_idx = id >> 5; + + /* Tollerate releasing ids that were never allocated with the allocator + * or are out of range... when we load Glade files with huge numbers it happens + * that loaded unallocated ids are out of range + */ + if (word_idx < allocator->n_words) + allocator->data[word_idx] |= 1 << (id & 31); + } +} + +#ifdef GLADE_ID_ALLOCATOR_TEST +int +main (int argc, char **argv) +{ + GladeIDAllocator *allocator = glade_id_allocator_new (); + guint i; + guint iter; + + for (i = 0; i < 1000; i++) + { + guint id = glade_id_allocator_allocate (allocator); + g_assert (id == i); + } + + for (i = 0; i < 1000; i++) + glade_id_allocator_release (allocator, i); + + for (iter = 0; iter < 10000; iter++) + { + for (i = 0; i < 1000; i++) + glade_id_allocator_allocate (allocator); + + for (i = 0; i < 1000; i++) + glade_id_allocator_release (allocator, i); + } + + glade_id_allocator_destroy (allocator); + + return 0; +} +#endif diff --git a/gladeui/glade-id-allocator.h b/gladeui/glade-id-allocator.h new file mode 100644 index 0000000..cab3913 --- /dev/null +++ b/gladeui/glade-id-allocator.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2004 Owen Taylor + * + * Authors: + * Owen Taylor + * + * Modified by the Glade developers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GLADE_ID_ALLOCATOR_H__ +#define __GLADE_ID_ALLOCATOR_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GladeIDAllocator GladeIDAllocator; + +GladeIDAllocator *glade_id_allocator_new (void); + +void glade_id_allocator_destroy (GladeIDAllocator *allocator); + +guint glade_id_allocator_allocate (GladeIDAllocator *allocator); + +void glade_id_allocator_release (GladeIDAllocator *allocator, + guint id); + +G_END_DECLS + +#endif /* __GLADE_ID_ALLOCATOR_H__ */ + diff --git a/gladeui/glade-inspector.c b/gladeui/glade-inspector.c new file mode 100644 index 0000000..33be091 --- /dev/null +++ b/gladeui/glade-inspector.c @@ -0,0 +1,1144 @@ +/* + * glade-inspector.h + * + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2007 Vincent Geddes + * + * Authors: + * Chema Celorio + * Tristan Van Berkom + * Vincent Geddes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +/** + * SECTION:glade-inspector + * @Short_Description: A widget for inspecting objects in a #GladeProject. + * + * A #GladeInspector is a widget for inspecting the objects that make up a user interface. + * + * An inspector is created by calling either glade_inspector_new() or glade_inspector_new_with_project(). + * The current project been inspected can be changed by calling glade_inspector_set_project(). + */ + +#include "glade.h" +#include "glade-widget.h" +#include "glade-project.h" +#include "glade-widget-adaptor.h" +#include "glade-inspector.h" +#include "glade-popup.h" +#include "glade-app.h" +#include "glade-dnd.h" + +#include +#include +#if GTK_CHECK_VERSION (2, 21, 8) +#include +#else +#include +#endif + +static void search_entry_text_inserted_cb (GtkEntry *entry, + const gchar *text, + gint length, + gint *position, + GladeInspector *inspector); +static void search_entry_text_deleted_cb (GtkEditable *editable, + gint start_pos, + gint end_pos, + GladeInspector *inspector); + +enum +{ + PROP_0, + PROP_PROJECT, + N_PROPERTIES +}; + +enum +{ + SELECTION_CHANGED, + ITEM_ACTIVATED, + LAST_SIGNAL +}; + +typedef struct _GladeInspectorPrivate +{ + GtkWidget *view; + GtkTreeModel *filter; + + GladeProject *project; + + GtkWidget *entry; + guint idle_complete; + gboolean search_disabled; + gchar *completion_text; + gchar *completion_text_fold; +} GladeInspectorPrivate; + +static GParamSpec *properties[N_PROPERTIES]; +static guint glade_inspector_signals[LAST_SIGNAL] = { 0 }; + + +static void glade_inspector_dispose (GObject *object); +static void glade_inspector_finalize (GObject *object); +static void add_columns (GtkTreeView *inspector); +static void item_activated_cb (GtkTreeView *view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GladeInspector *inspector); +static void selection_changed_cb (GtkTreeSelection *selection, + GladeInspector *inspector); +static gint button_press_cb (GtkWidget *widget, + GdkEventButton *event, + GladeInspector *inspector); + +G_DEFINE_TYPE_WITH_PRIVATE (GladeInspector, glade_inspector, GTK_TYPE_BOX) + +static void +glade_inspector_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeInspector *inspector = GLADE_INSPECTOR (object); + + switch (property_id) + { + case PROP_PROJECT: + glade_inspector_set_project (inspector, g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +glade_inspector_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GladeInspector *inspector = GLADE_INSPECTOR (object); + + switch (property_id) + { + case PROP_PROJECT: + g_value_set_object (value, glade_inspector_get_project (inspector)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +glade_inspector_class_init (GladeInspectorClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = glade_inspector_dispose; + object_class->finalize = glade_inspector_finalize; + object_class->set_property = glade_inspector_set_property; + object_class->get_property = glade_inspector_get_property; + + /** + * GladeInspector::selection-changed: + * @inspector: the object which received the signal + * + * Emitted when the selection changes in the GladeInspector. + */ + glade_inspector_signals[SELECTION_CHANGED] = + g_signal_new ("selection-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeInspectorClass, selection_changed), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GladeInspector::item-activated: + * @inspector: the object which received the signal + * + * Emitted when a item is activated in the GladeInspector. + */ + glade_inspector_signals[ITEM_ACTIVATED] = + g_signal_new ("item-activated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeInspectorClass, item_activated), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + properties[PROP_PROJECT] = + g_param_spec_object ("project", + _("Project"), + _("The project being inspected"), + GLADE_TYPE_PROJECT, + G_PARAM_READABLE | G_PARAM_WRITABLE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + +static gboolean +glade_inspector_visible_func (GtkTreeModel *model, + GtkTreeIter *parent, + gpointer data) +{ + GladeInspector *inspector = data; + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + GtkTreeIter iter; + + gboolean retval = FALSE; + + if (priv->search_disabled || priv->completion_text == NULL) + return TRUE; + + if (gtk_tree_model_iter_children (model, &iter, parent)) + { + do + { + retval = glade_inspector_visible_func (model, &iter, data); + } + while (gtk_tree_model_iter_next (model, &iter) && !retval); + } + + if (!retval) + { + gchar *widget_name, *haystack; + + gtk_tree_model_get (model, parent, GLADE_PROJECT_MODEL_COLUMN_NAME, + &widget_name, -1); + + haystack = g_utf8_casefold (widget_name, -1); + + retval = strstr (haystack, priv->completion_text_fold) != NULL; + + g_free (haystack); + g_free (widget_name); + } + + return retval; +} + +static void +glade_inspector_refilter (GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + if (!priv->search_disabled) + { + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter)); + gtk_tree_view_expand_all (GTK_TREE_VIEW (priv->view)); + } +} + +static void +search_entry_changed_cb (GtkEntry *entry, GladeInspector *inspector) +{ + glade_inspector_refilter (inspector); +} + +typedef struct { + const gchar *text; + gchar *common_text; + gchar *first_match; +} CommonMatchData; + +static void +reduce_string (gchar *str1, + const gchar *str2) +{ + gint str1len = strlen (str1); + gint i; + + for (i = 0; str2[i] != '\0'; i++) + { + + if (str1[i] != str2[i] || i >= str1len) + { + str1[i] = '\0'; + break; + } + } + + if (str2[i] == '\0') + str1[i] = '\0'; +} + +static gboolean +search_common_matches (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + CommonMatchData *data) +{ + g_autoptr(GObject) obj = NULL; + GladeWidget *gwidget; + const gchar *name; + gboolean match; + + gtk_tree_model_get (model, iter, GLADE_PROJECT_MODEL_COLUMN_OBJECT, &obj, -1); + gwidget = glade_widget_get_from_gobject (obj); + + if (!glade_widget_has_name (gwidget)) + return FALSE; + + name = glade_widget_get_name (gwidget); + match = (strncmp (data->text, name, strlen (data->text)) == 0); + + if (match) + { + if (!data->first_match) + data->first_match = g_strdup (name); + + if (data->common_text) + reduce_string (data->common_text, name); + else + data->common_text = g_strdup (name); + } + + return FALSE; +} + +/* Returns the shortest common matching text from all + * project widget names. + * + * If shortest_match is specified, it is given the first + * full match for the 'search' text + */ +static gchar * +get_partial_match (GladeInspector *inspector, + const gchar *search, + gchar **first_match) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + GtkTreeModel *model = GTK_TREE_MODEL (priv->project); + CommonMatchData data; + + data.text = search; + data.common_text = NULL; + data.first_match = NULL; + + gtk_tree_model_foreach (model, (GtkTreeModelForeachFunc)search_common_matches, &data); + + if (first_match) + *first_match = data.first_match; + else + g_free (data.first_match); + + return data.common_text; +} + +static void +inspector_set_completion_text (GladeInspector *inspector, const gchar *text) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + g_free (priv->completion_text); + priv->completion_text = g_strdup (text); + priv->completion_text_fold = text ? g_utf8_casefold (text, -1) : NULL; + +} + +static gboolean +search_complete_idle (GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + gchar *completed; + const gchar *str; + gsize length; + + str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); + + completed = get_partial_match (inspector, str, NULL); + + inspector_set_completion_text (inspector, str); + + if (completed) + { + length = strlen (str); + + g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector); + g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector); + + gtk_entry_set_text (GTK_ENTRY (priv->entry), completed); + gtk_editable_set_position (GTK_EDITABLE (priv->entry), length); + gtk_editable_select_region (GTK_EDITABLE (priv->entry), length, -1); + g_free (completed); + + g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector); + g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector); + + } + + priv->idle_complete = 0; + + return FALSE; +} + +static void +search_entry_text_inserted_cb (GtkEntry *entry, + const gchar *text, + gint length, + gint *position, + GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + if (!priv->search_disabled && !priv->idle_complete) + { + priv->idle_complete = + g_idle_add ((GSourceFunc) search_complete_idle, inspector); + } +} + +static void +search_entry_text_deleted_cb (GtkEditable *editable, + gint start_pos, + gint end_pos, + GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + if (!priv->search_disabled) + { + inspector_set_completion_text (inspector, gtk_entry_get_text (GTK_ENTRY (priv->entry))); + glade_inspector_refilter (inspector); + } +} + +static gboolean +search_entry_key_press_event_cb (GtkEntry *entry, + GdkEventKey *event, + GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + const gchar *str; + + str = gtk_entry_get_text (GTK_ENTRY (priv->entry)); + + if (event->keyval == GDK_KEY_Tab) + { + /* CNTL-Tab: An escape route to move focus */ + if (event->state & GDK_CONTROL_MASK) + { + gtk_widget_grab_focus (priv->view); + } + else /* Tab: Move cursor forward and refine the filter to include all text */ + { + inspector_set_completion_text (inspector, str); + + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); + + glade_inspector_refilter (inspector); + } + return TRUE; + } + + /* Enter/Return: Find the first complete match, refine filter to the complete match, and select the match */ + if (event->keyval == GDK_KEY_Return || event->keyval == GDK_KEY_KP_Enter) + { + gchar *name, *full_match = NULL; + + if (str && (name = get_partial_match (inspector, str, &full_match))) + { + GladeWidget *widget; + + inspector_set_completion_text (inspector, full_match); + g_free (name); + + g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector); + g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector); + + gtk_entry_set_text (GTK_ENTRY (entry), priv->completion_text); + + g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector); + g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector); + + gtk_editable_set_position (GTK_EDITABLE (entry), -1); + gtk_editable_select_region (GTK_EDITABLE (entry), -1, -1); + + glade_inspector_refilter (inspector); + + widget = glade_project_get_widget_by_name (priv->project, priv->completion_text); + if (widget) + glade_project_selection_set (priv->project, glade_widget_get_object (widget), TRUE); + } + return TRUE; + } + + /* Backspace: Attempt to move the cursor backwards and maintain the completed/selected portion */ + if (event->keyval == GDK_KEY_BackSpace) + { + if (!priv->search_disabled && !priv->idle_complete && str && str[0]) + { + /* Now, set the text to the current completion text -1 char, and recomplete */ + if (priv->completion_text && priv->completion_text[0]) + { + /* If we're not at the position of the length of the completion text, just carry on */ + if (!gtk_editable_get_selection_bounds (GTK_EDITABLE (priv->entry), NULL, NULL)) + return FALSE; + + priv->completion_text[strlen (priv->completion_text) -1] = '\0'; + + g_signal_handlers_block_by_func (priv->entry, search_entry_text_inserted_cb, inspector); + g_signal_handlers_block_by_func (priv->entry, search_entry_text_deleted_cb, inspector); + + gtk_entry_set_text (GTK_ENTRY (priv->entry), priv->completion_text); + gtk_editable_set_position (GTK_EDITABLE (priv->entry), -1); + + g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_inserted_cb, inspector); + g_signal_handlers_unblock_by_func (priv->entry, search_entry_text_deleted_cb, inspector); + + priv->idle_complete = + g_idle_add ((GSourceFunc) search_complete_idle, inspector); + + return TRUE; + } + } + } + + return FALSE; +} + +static gboolean +search_entry_focus_in_cb (GtkWidget *entry, + GdkEventFocus *event, + GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + if (priv->search_disabled) + priv->search_disabled = FALSE; + + return FALSE; +} + +static gboolean +search_entry_focus_out_cb (GtkWidget *entry, + GdkEventFocus *event, + GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + priv->search_disabled = TRUE; + + inspector_set_completion_text (inspector, NULL); + + gtk_entry_set_text (GTK_ENTRY (priv->entry), ""); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (priv->filter)); + + return FALSE; +} + +static void +glade_inspector_init (GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + GtkWidget *sw; + GtkTreeSelection *selection; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (inspector), + GTK_ORIENTATION_VERTICAL); + + priv->project = NULL; + + priv->entry = gtk_entry_new (); + + gtk_entry_set_placeholder_text (GTK_ENTRY (priv->entry), _(" < Search Widgets >")); + gtk_widget_show (priv->entry); + gtk_box_pack_start (GTK_BOX (inspector), priv->entry, FALSE, FALSE, 2); + + g_signal_connect (priv->entry, "changed", + G_CALLBACK (search_entry_changed_cb), inspector); + g_signal_connect (priv->entry, "key-press-event", + G_CALLBACK (search_entry_key_press_event_cb), inspector); + g_signal_connect_after (priv->entry, "insert-text", + G_CALLBACK (search_entry_text_inserted_cb), + inspector); + g_signal_connect_after (priv->entry, "delete-text", + G_CALLBACK (search_entry_text_deleted_cb), + inspector); + g_signal_connect (priv->entry, "focus-in-event", + G_CALLBACK (search_entry_focus_in_cb), inspector); + g_signal_connect (priv->entry, "focus-out-event", + G_CALLBACK (search_entry_focus_out_cb), inspector); + + priv->view = gtk_tree_view_new (); + gtk_tree_view_set_enable_search (GTK_TREE_VIEW (priv->view), FALSE); + gtk_scrollable_set_hscroll_policy (GTK_SCROLLABLE (priv->view), GTK_SCROLL_MINIMUM); + add_columns (GTK_TREE_VIEW (priv->view)); + + /* Set it as a drag source */ + gtk_tree_view_enable_model_drag_source (GTK_TREE_VIEW (priv->view), + GDK_BUTTON1_MASK, + _glade_dnd_get_target (), 1, GDK_ACTION_MOVE | GDK_ACTION_COPY); + + g_signal_connect (G_OBJECT (priv->view), "row-activated", + G_CALLBACK (item_activated_cb), inspector); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)); + gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE); + g_signal_connect (G_OBJECT (selection), "changed", + G_CALLBACK (selection_changed_cb), inspector); + + /* Expand All */ + gtk_entry_set_icon_from_icon_name (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, "go-down"); + gtk_entry_set_icon_tooltip_text (GTK_ENTRY (priv->entry), GTK_ENTRY_ICON_SECONDARY, _("Expand all")); + g_signal_connect_swapped (priv->entry, "icon-press", G_CALLBACK (gtk_tree_view_expand_all), priv->view); + + /* popup menu */ + g_signal_connect (G_OBJECT (priv->view), "button-press-event", + G_CALLBACK (button_press_cb), inspector); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + gtk_container_add (GTK_CONTAINER (sw), priv->view); + gtk_box_pack_start (GTK_BOX (inspector), sw, TRUE, TRUE, 0); + + gtk_widget_show (priv->view); + gtk_widget_show (sw); +} + +static void +glade_inspector_dispose (GObject *object) +{ + GladeInspector *inspector = GLADE_INSPECTOR (object); + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + glade_inspector_set_project (inspector, NULL); + + if (priv->idle_complete) + { + g_source_remove (priv->idle_complete); + priv->idle_complete = 0; + } + + G_OBJECT_CLASS (glade_inspector_parent_class)->dispose (object); +} + +static void +glade_inspector_finalize (GObject *object) +{ + GladeInspector *inspector = GLADE_INSPECTOR (object); + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + g_free (priv->completion_text); + g_free (priv->completion_text_fold); + + G_OBJECT_CLASS (glade_inspector_parent_class)->finalize (object); +} + +static void +project_selection_changed_cb (GladeProject *project, + GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + GladeWidget *widget; + GtkTreeSelection *selection; + GtkTreeModel *model; + GtkTreeIter *iter; + GtkTreePath *path, *ancestor_path; + GList *list; + + g_return_if_fail (GLADE_IS_INSPECTOR (inspector)); + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (priv->project == project); + + g_signal_handlers_block_by_func (gtk_tree_view_get_selection + (GTK_TREE_VIEW (priv->view)), + G_CALLBACK (selection_changed_cb), + inspector); + + selection = + gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)); + g_return_if_fail (selection != NULL); + + model = priv->filter; + + gtk_tree_selection_unselect_all (selection); + + for (list = glade_project_selection_get (project); + list && list->data; list = list->next) + { + if ((widget = + glade_widget_get_from_gobject (G_OBJECT (list->data))) != NULL) + { + if ((iter = + glade_util_find_iter_by_widget (model, widget, + GLADE_PROJECT_MODEL_COLUMN_OBJECT)) + != NULL) + { + path = gtk_tree_model_get_path (model, iter); + ancestor_path = gtk_tree_path_copy (path); + + /* expand parent node */ + if (gtk_tree_path_up (ancestor_path)) + gtk_tree_view_expand_to_path + (GTK_TREE_VIEW (priv->view), ancestor_path); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW + (priv->view), path, NULL, + TRUE, 0.5, 0); + + gtk_tree_selection_select_iter (selection, iter); + + gtk_tree_iter_free (iter); + gtk_tree_path_free (path); + gtk_tree_path_free (ancestor_path); + } + } + } + + g_signal_handlers_unblock_by_func (gtk_tree_view_get_selection + (GTK_TREE_VIEW (priv->view)), + G_CALLBACK (selection_changed_cb), + inspector); +} + +static void +selection_foreach_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + GList **selection) +{ + g_autoptr(GObject) object = NULL; + + gtk_tree_model_get (model, iter, + GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, + -1); + + if (object) + *selection = g_list_prepend (*selection, object); +} + +static void +selection_changed_cb (GtkTreeSelection *selection, GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + GList *sel = NULL, *l; + + gtk_tree_selection_selected_foreach (selection, + (GtkTreeSelectionForeachFunc) + selection_foreach_func, &sel); + + /* We dont modify the project selection for a change that + * leaves us with no selection. + * + * This is typically because the user is changing the name + * of a widget and the filter is active, the new name causes + * the row to go out of the model and the selection to be + * cleared, if we clear the selection we remove the editor + * that the user is trying to type into. + */ + if (!sel) + return; + + g_signal_handlers_block_by_func (priv->project, + G_CALLBACK (project_selection_changed_cb), + inspector); + + glade_project_selection_clear (priv->project, FALSE); + for (l = sel; l; l = l->next) + glade_project_selection_add (priv->project, G_OBJECT (l->data), FALSE); + glade_project_selection_changed (priv->project); + g_list_free (sel); + + g_signal_handlers_unblock_by_func (priv->project, + G_CALLBACK (project_selection_changed_cb), + inspector); + + g_signal_emit (inspector, glade_inspector_signals[SELECTION_CHANGED], 0); +} + +static void +item_activated_cb (GtkTreeView *view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GladeInspector *inspector) +{ + g_signal_emit (inspector, glade_inspector_signals[ITEM_ACTIVATED], 0); +} + +static gint +button_press_cb (GtkWidget *widget, + GdkEventButton *event, + GladeInspector *inspector) +{ + GtkTreeView *view = GTK_TREE_VIEW (widget); + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + GtkTreePath *path = NULL; + gboolean handled = FALSE; + + /* Give some kind of access in case of missing right button */ + if (event->window == gtk_tree_view_get_bin_window (view) && + glade_popup_is_popup_event (event)) + { + if (gtk_tree_view_get_path_at_pos (view, (gint) event->x, (gint) event->y, + &path, NULL, + NULL, NULL) && path != NULL) + { + GtkTreeIter iter; + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (priv->project), + &iter, path)) + { + g_autoptr (GladeWidget) object = NULL; + + /* now we can obtain the widget from the iter. + */ + gtk_tree_model_get (GTK_TREE_MODEL (priv->project), &iter, + GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, + -1); + + if (widget != NULL) + glade_popup_widget_pop (glade_widget_get_from_gobject (object), + event, TRUE); + else + glade_popup_simple_pop (priv->project, event); + + handled = TRUE; + + gtk_tree_path_free (path); + } + } + else + { + glade_popup_simple_pop (priv->project, event); + handled = TRUE; + } + } + return handled; +} + +static void +glade_inspector_warning_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *warning = NULL; + + gtk_tree_model_get (model, iter, + GLADE_PROJECT_MODEL_COLUMN_WARNING, &warning, + -1); + + g_object_set (renderer, "visible", warning != NULL, NULL); + + g_free (warning); +} + +static void +glade_inspector_name_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + g_autoptr(GObject) obj = NULL; + GladeWidget *gwidget; + + gtk_tree_model_get (model, iter, + GLADE_PROJECT_MODEL_COLUMN_OBJECT, &obj, + -1); + + gwidget = glade_widget_get_from_gobject (obj); + + g_object_set (renderer, "text", + (glade_widget_has_name (gwidget)) ? + glade_widget_get_display_name (gwidget) : NULL, + NULL); +} + + +static void +glade_inspector_detail_cell_data_func (GtkTreeViewColumn *column, + GtkCellRenderer *renderer, + GtkTreeModel *model, + GtkTreeIter *iter, + gpointer data) +{ + gchar *type_name = NULL, *detail = NULL; + + gtk_tree_model_get (model, iter, + GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME, &type_name, + GLADE_PROJECT_MODEL_COLUMN_MISC, &detail, + -1); + + if (detail) + { + gchar *final = g_strconcat (type_name, " ", detail, NULL); + + g_object_set (renderer, "text", final, NULL); + + g_free (final); + } + else + g_object_set (renderer, "text", type_name, NULL); + + g_free (type_name); + g_free (detail); +} + + + + +static void +add_columns (GtkTreeView *view) +{ + GtkTreeViewColumn *column; + GtkCellRenderer *renderer; + GtkCellAreaBox *box; + + /* Use a GtkCellArea box to set the alignments manually */ + box = (GtkCellAreaBox *)gtk_cell_area_box_new (); + + column = gtk_tree_view_column_new_with_area (GTK_CELL_AREA (box)); + gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_AUTOSIZE); + gtk_cell_area_box_set_spacing (GTK_CELL_AREA_BOX (box), 2); + + gtk_tree_view_set_tooltip_column (view, GLADE_PROJECT_MODEL_COLUMN_WARNING); + + /* Padding */ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "width", 4, NULL); + gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); + + /* Warning cell */ + renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (renderer, + "icon-name", "dialog-warning-symbolic", + "xpad", 2, + NULL); + gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); + gtk_tree_view_column_set_cell_data_func (column, renderer, + glade_inspector_warning_cell_data_func, + NULL, NULL); + + /* Class Icon */ + renderer = gtk_cell_renderer_pixbuf_new (); + g_object_set (renderer, + "xpad", 2, + NULL); + gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); + gtk_tree_view_column_set_attributes (column, + renderer, + "icon_name", + GLADE_PROJECT_MODEL_COLUMN_ICON_NAME, + NULL); + + /* Widget Name */ + renderer = gtk_cell_renderer_text_new (); + gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); + gtk_tree_view_column_set_attributes (column, + renderer, + "text", GLADE_PROJECT_MODEL_COLUMN_NAME, + NULL); + gtk_tree_view_column_set_cell_data_func (column, renderer, + glade_inspector_name_cell_data_func, + NULL, NULL); + + /* Padding */ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), "width", 8, NULL); + gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); + + /* Class name & detail */ + renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (renderer), + "style", PANGO_STYLE_ITALIC, + "foreground", "Gray", + "ellipsize", PANGO_ELLIPSIZE_END, + NULL); + + gtk_cell_area_box_pack_start (box, renderer, FALSE, FALSE, FALSE); + gtk_tree_view_column_set_cell_data_func (column, renderer, + glade_inspector_detail_cell_data_func, + NULL, NULL); + + gtk_tree_view_append_column (view, column); + gtk_tree_view_set_headers_visible (view, FALSE); +} + +static void +disconnect_project_signals (GladeInspector *inspector, GladeProject *project) +{ + g_signal_handlers_disconnect_by_func (G_OBJECT (project), + G_CALLBACK + (project_selection_changed_cb), + inspector); +} + +static void +connect_project_signals (GladeInspector *inspector, GladeProject *project) +{ + g_signal_connect (G_OBJECT (project), "selection-changed", + G_CALLBACK (project_selection_changed_cb), inspector); +} + +/** + * glade_inspector_set_project: + * @inspector: a #GladeInspector + * @project: a #GladeProject + * + * Sets the current project of @inspector to @project. To unset the current + * project, pass %NULL for @project. + */ +void +glade_inspector_set_project (GladeInspector *inspector, GladeProject *project) +{ + g_return_if_fail (GLADE_IS_INSPECTOR (inspector)); + g_return_if_fail (GLADE_IS_PROJECT (project) || project == NULL); + + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + if (priv->project == project) + return; + + if (priv->project) + { + disconnect_project_signals (inspector, priv->project); + + /* Release our filter which releases the project */ + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->view), NULL); + priv->filter = NULL; + priv->project = NULL; + } + + if (project) + { + priv->project = project; + + /* The filter holds our reference to 'project' */ + priv->filter = + gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->project), NULL); + + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER + (priv->filter), + (GtkTreeModelFilterVisibleFunc) + glade_inspector_visible_func, + inspector, NULL); + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->view), priv->filter); + g_object_unref (priv->filter); /* pass ownership of the filter to the model */ + + connect_project_signals (inspector, project); + } + + g_object_notify_by_pspec (G_OBJECT (inspector), properties[PROP_PROJECT]); +} + +/** + * glade_inspector_get_project: + * @inspector: a #GladeInspector + * + * Note that the method does not ref the returned #GladeProject. + * + * Returns: (transfer none): A #GladeProject + */ +GladeProject * +glade_inspector_get_project (GladeInspector *inspector) +{ + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + g_return_val_if_fail (GLADE_IS_INSPECTOR (inspector), NULL); + + return priv->project; +} + +/** + * glade_inspector_get_selected_items: + * @inspector: a #GladeInspector + * + * Returns the selected items in the inspector. + * + * Returns: (transfer container) (element-type GladeWidget): A #GList of #GladeWidget + */ +GList * +glade_inspector_get_selected_items (GladeInspector *inspector) +{ + GtkTreeSelection *selection; + GList *items = NULL, *paths; + GladeInspectorPrivate *priv = glade_inspector_get_instance_private (inspector); + + selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->view)); + + for (paths = gtk_tree_selection_get_selected_rows (selection, NULL); + paths != NULL; paths = g_list_next (paths->next)) + { + GtkTreeIter filter_iter; + GtkTreeIter iter; + GtkTreePath *path = (GtkTreePath *) paths->data; + g_autoptr(GObject) object = NULL; + + gtk_tree_model_get_iter (priv->filter, &filter_iter, path); + gtk_tree_model_filter_convert_iter_to_child_iter (GTK_TREE_MODEL_FILTER + (priv->filter), &iter, + &filter_iter); + gtk_tree_model_get (GTK_TREE_MODEL (priv->project), &iter, + GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, -1); + + items = g_list_prepend (items, glade_widget_get_from_gobject (object)); + } + + g_list_free_full (paths, (GDestroyNotify) gtk_tree_path_free); + + return items; +} + +/** + * glade_inspector_new: + * + * Creates a new #GladeInspector + * + * Returns: (transfer full): a new #GladeInspector + */ +GtkWidget * +glade_inspector_new (void) +{ + return g_object_new (GLADE_TYPE_INSPECTOR, NULL); +} + +/** + * glade_inspector_new_with_project: + * @project: a #GladeProject + * + * Creates a new #GladeInspector with @project + * + * Returns: (transfer full): a new #GladeInspector + */ +GtkWidget * +glade_inspector_new_with_project (GladeProject *project) +{ + GladeInspector *inspector; + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + inspector = g_object_new (GLADE_TYPE_INSPECTOR, "project", project, NULL); + + /* Make sure we expended to the right path */ + project_selection_changed_cb (project, inspector); + + return GTK_WIDGET (inspector); +} diff --git a/gladeui/glade-inspector.h b/gladeui/glade-inspector.h new file mode 100644 index 0000000..d2aa5e5 --- /dev/null +++ b/gladeui/glade-inspector.h @@ -0,0 +1,65 @@ +/* + * glade-inspector.h + * + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2007 Vincent Geddes + * + * Authors: + * Chema Celorio + * Tristan Van Berkom + * Vincent Geddes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __GLADE_INSPECTOR_H__ +#define __GLADE_INSPECTOR_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_INSPECTOR glade_inspector_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeInspector, glade_inspector, GLADE, INSPECTOR, GtkBox) + +/** + * GladeInspector: + * + * The #GladeInspector struct contains private data only, and should be manipulated using the functions below. + * + */ + +struct _GladeInspectorClass +{ + GtkBoxClass parent_class; + + void (* selection_changed) (GladeInspector *inspector); + void (* item_activated) (GladeInspector *inspector); + + gpointer padding[4]; +}; + +GtkWidget *glade_inspector_new (void); +GtkWidget *glade_inspector_new_with_project (GladeProject *project); +void glade_inspector_set_project (GladeInspector *inspector, + GladeProject *project); +GladeProject *glade_inspector_get_project (GladeInspector *inspector); +GList *glade_inspector_get_selected_items (GladeInspector *inspector); + +G_END_DECLS + +#endif /* __GLADE_INSPECTOR_H__ */ diff --git a/gladeui/glade-marshallers.list b/gladeui/glade-marshallers.list new file mode 100644 index 0000000..1aaa3d2 --- /dev/null +++ b/gladeui/glade-marshallers.list @@ -0,0 +1,25 @@ +VOID:POINTER,POINTER +VOID:POINTER +VOID:STRING,ULONG,UINT,STRING +VOID:OBJECT +VOID:STRING +VOID:INT,INT +VOID:OBJECT,OBJECT +VOID:OBJECT,OBJECT,OBJECT +VOID:OBJECT,BOOLEAN +VOID:STRING,STRING,STRING +OBJECT:POINTER +OBJECT:OBJECT,UINT +BOOLEAN:STRING +BOOLEAN:BOXED +BOOLEAN:OBJECT +BOOLEAN:OBJECT,BOXED +BOOLEAN:OBJECT,POINTER +BOOLEAN:OBJECT,BOOLEAN +BOOLEAN:OBJECT,OBJECT +BOOLEAN:OBJECT,STRING +BOOLEAN:STRING,STRING,STRING,BOXED +BOOLEAN:STRING,BOXED,OBJECT +STRING:OBJECT +INT:OBJECT,BOXED +BOXED:OBJECT diff --git a/gladeui/glade-name-context.c b/gladeui/glade-name-context.c new file mode 100644 index 0000000..39bded7 --- /dev/null +++ b/gladeui/glade-name-context.c @@ -0,0 +1,196 @@ +/* + * glade-name-context.c + * + * Copyright (C) 2008 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include + +#include "glade-id-allocator.h" +#include "glade-name-context.h" + +struct _GladeNameContext +{ + GHashTable *name_allocators; + + GHashTable *names; +}; + +/** + * glade_name_context_new: (skip) + * + * Creates a new name context + * + * Returns: a new GladeNameContext + */ +GladeNameContext * +glade_name_context_new (void) +{ + GladeNameContext *context = g_slice_new0 (GladeNameContext); + + context->name_allocators = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + (GDestroyNotify) + glade_id_allocator_destroy); + + context->names = g_hash_table_new_full (g_str_hash, + g_str_equal, g_free, NULL); + + return context; +} + +void +glade_name_context_destroy (GladeNameContext *context) +{ + g_return_if_fail (context != NULL); + + g_hash_table_destroy (context->name_allocators); + g_hash_table_destroy (context->names); + g_slice_free (GladeNameContext, context); +} + +gchar * +glade_name_context_new_name (GladeNameContext *context, + const gchar *base_name) +{ + GladeIDAllocator *id_allocator; + const gchar *number; + gchar *name = NULL, *freeme = NULL; + guint i = 1; + + g_return_val_if_fail (context != NULL, NULL); + g_return_val_if_fail (base_name && base_name[0], NULL); + + number = base_name + strlen (base_name); + while (number > base_name && g_ascii_isdigit (number[-1])) + --number; + + if (*number) + { + freeme = g_strndup (base_name, number - base_name); + base_name = freeme; + } + + id_allocator = g_hash_table_lookup (context->name_allocators, base_name); + + if (id_allocator == NULL) + { + id_allocator = glade_id_allocator_new (); + g_hash_table_insert (context->name_allocators, + g_strdup (base_name), id_allocator); + } + + do + { + g_free (name); + i = glade_id_allocator_allocate (id_allocator); + name = g_strdup_printf ("%s%u", base_name, i); + } + while (glade_name_context_has_name (context, name)); + + g_free (freeme); + return name; +} + +guint +glade_name_context_n_names (GladeNameContext *context) +{ + g_return_val_if_fail (context != NULL, FALSE); + + return g_hash_table_size (context->names); +} + +gboolean +glade_name_context_has_name (GladeNameContext *context, const gchar *name) +{ + g_return_val_if_fail (context != NULL, FALSE); + g_return_val_if_fail (name && name[0], FALSE); + + return (g_hash_table_lookup (context->names, name) != NULL); +} + +gboolean +glade_name_context_add_name (GladeNameContext *context, const gchar *name) +{ + gboolean ret = FALSE; + + g_return_val_if_fail (context != NULL, FALSE); + g_return_val_if_fail (name && name[0], FALSE); + + if (!glade_name_context_has_name (context, name)) + { + g_hash_table_insert (context->names, g_strdup (name), + GINT_TO_POINTER (TRUE)); + ret = TRUE; + } + + return ret; +} + +void +glade_name_context_release_name (GladeNameContext *context, const gchar *name) +{ + + const gchar *first_number = name; + gchar *end_number, *base_name; + GladeIDAllocator *id_allocator; + gunichar ch; + gint id; + + g_return_if_fail (context != NULL); + g_return_if_fail (name && name[0]); + + /* Remove from name hash first... */ + g_hash_table_remove (context->names, name); + + do + { + ch = g_utf8_get_char (first_number); + + if (ch == 0 || g_unichar_isdigit (ch)) + break; + + first_number = g_utf8_next_char (first_number); + } + while (TRUE); + + /* if there is a number - then we have to unallocate it... */ + if (ch == 0) + return; + + base_name = g_strdup (name); + *(base_name + (first_number - name)) = 0; + + if ((id_allocator = + g_hash_table_lookup (context->name_allocators, base_name)) != NULL) + { + id = (int) strtol (first_number, &end_number, 10); + if (*end_number == 0) + glade_id_allocator_release (id_allocator, id); + } + + g_free (base_name); +} diff --git a/gladeui/glade-name-context.h b/gladeui/glade-name-context.h new file mode 100644 index 0000000..ddc360f --- /dev/null +++ b/gladeui/glade-name-context.h @@ -0,0 +1,29 @@ +#ifndef __GLADE_NAME_CONTEXT_H__ +#define __GLADE_NAME_CONTEXT_H__ + +#include + +G_BEGIN_DECLS + +typedef struct _GladeNameContext GladeNameContext; + +GladeNameContext *glade_name_context_new (void); +void glade_name_context_destroy (GladeNameContext *context); + +gchar *glade_name_context_new_name (GladeNameContext *context, + const gchar *base_name); + +guint glade_name_context_n_names (GladeNameContext *context); + +gboolean glade_name_context_has_name (GladeNameContext *context, + const gchar *name); + +gboolean glade_name_context_add_name (GladeNameContext *context, + const gchar *name); + +void glade_name_context_release_name (GladeNameContext *context, + const gchar *name); + +G_END_DECLS + +#endif /* __GLADE_NAME_CONTEXT_H__ */ diff --git a/gladeui/glade-named-icon-chooser-dialog.c b/gladeui/glade-named-icon-chooser-dialog.c new file mode 100644 index 0000000..acb8a1e --- /dev/null +++ b/gladeui/glade-named-icon-chooser-dialog.c @@ -0,0 +1,1781 @@ +/* + * glade-named-icon-chooser-widget.c - Named icon chooser widget + * + * Copyright (C) 2007 Vincent Geddes + * + * Author: Vincent Geddes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANNAMED_ICON_CHOOSERILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include + +#include "glade-private.h" +#include "glade-named-icon-chooser-dialog.h" +#include "icon-naming-spec.c" + +#include +#include +#include +#include + + +#define DEFAULT_SETTING_LIST_STANDARD_ONLY TRUE + +enum +{ + CONTEXTS_ID_COLUMN, + CONTEXTS_NAME_COLUMN, + CONTEXTS_TITLE_COLUMN, + + CONTEXTS_N_COLUMS +}; + +enum +{ + ICONS_CONTEXT_COLUMN, + ICONS_STANDARD_COLUMN, + ICONS_NAME_COLUMN, + + ICONS_N_COLUMNS +}; + +enum +{ + GLADE_NAMED_ICON +}; + +enum +{ + ICON_ACTIVATED, + SELECTION_CHANGED, + + LAST_SIGNAL +}; + +typedef struct _GladeNamedIconChooserDialogPrivate +{ + GtkWidget *icons_view; + GtkTreeModel *filter_model; /* filtering model */ + GtkListStore *icons_store; /* data store */ + GtkTreeSelection *selection; + + GtkWidget *contexts_view; + GtkListStore *contexts_store; + + GtkWidget *entry; + GtkEntryCompletion *entry_completion; + + GtkWidget *button; /* list-standard-only checkbutton */ + + gint context_id; /* current icon name context for icon filtering */ + + gchar *pending_select_name; /* an icon name for a pending treeview selection. + * can only select name after model is loaded + * and the widget is mapped */ + + GtkIconTheme *icon_theme; /* the current icon theme */ + guint load_id; /* id of the idle function for loading data into model */ + + gboolean settings_list_standard; /* whether to list standard icon names only */ + + GtkWidget *last_focus_widget; + + gboolean icons_loaded; /* whether the icons have been loaded into the model */ +} GladeNamedIconChooserDialogPrivate; + +static GHashTable *standard_icon_quarks = NULL; + +static guint dialog_signals[LAST_SIGNAL] = { 0, }; + +gchar * +glade_named_icon_chooser_dialog_get_icon_name (GladeNamedIconChooserDialog *dialog); + +void +glade_named_icon_chooser_dialog_set_icon_name (GladeNamedIconChooserDialog *dialog, + const gchar *icon_name); + +gboolean +glade_named_icon_chooser_dialog_set_context (GladeNamedIconChooserDialog *dialog, + const gchar *context); + +gchar * +glade_named_icon_chooser_dialog_get_context (GladeNamedIconChooserDialog *dialog); + +static gboolean should_respond (GladeNamedIconChooserDialog *dialog); + +static void filter_icons_model (GladeNamedIconChooserDialog *dialog); + +static gboolean scan_for_name_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); + +static gboolean scan_for_context_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer data); + +static void settings_load (GladeNamedIconChooserDialog *dialog); + +static void settings_save (GladeNamedIconChooserDialog *dialog); + + +G_DEFINE_TYPE_WITH_PRIVATE (GladeNamedIconChooserDialog, + glade_named_icon_chooser_dialog, + GTK_TYPE_DIALOG) + + +static void +entry_set_name (GladeNamedIconChooserDialog *dialog, const gchar *name) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + /* Must disable completion before setting text, in order to avoid + * spurious warnings (possible GTK+ bug). + */ + gtk_entry_set_completion (GTK_ENTRY (priv->entry), NULL); + + gtk_entry_set_text (GTK_ENTRY (priv->entry), name); + + gtk_entry_set_completion (GTK_ENTRY (priv->entry), + priv->entry_completion); +} + +static GtkIconTheme * +get_icon_theme_for_widget (GtkWidget *widget) +{ + if (gtk_widget_has_screen (widget)) + return gtk_icon_theme_get_for_screen (gtk_widget_get_screen (widget)); + + return gtk_icon_theme_get_default (); +} + +/* validates name according to the icon naming spec (en_US.US_ASCII [a-z1-9_-.]) */ +static gboolean +is_well_formed (const gchar * name) +{ + gchar *c = (gchar *) name; + for (; *c; c++) + { + if (g_ascii_isalnum (*c)) + { + if (g_ascii_isalpha (*c) && !g_ascii_islower (*c)) + return FALSE; + } + else if (*c != '_' && *c != '-' && *c != '.') + { + return FALSE; + } + } + return TRUE; +} + +static void +check_entry_text (GladeNamedIconChooserDialog *dialog, + gchar **name_ret, + gboolean *is_wellformed_ret, + gboolean *is_empty_ret) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + if (strlen (gtk_entry_get_text (GTK_ENTRY (priv->entry))) == 0) + { + *name_ret = NULL; + *is_wellformed_ret = TRUE; + *is_empty_ret = TRUE; + + return; + } + + *is_empty_ret = FALSE; + + *is_wellformed_ret = + is_well_formed (gtk_entry_get_text (GTK_ENTRY (priv->entry))); + + if (*is_wellformed_ret) + *name_ret = g_strdup (gtk_entry_get_text (GTK_ENTRY (priv->entry))); + else + *name_ret = NULL; +} + +static void +changed_text_handler (GtkEditable *editable, + GladeNamedIconChooserDialog *dialog) +{ + g_signal_emit_by_name (dialog, "selection-changed", NULL); +} + +/* ensure that only valid text can be inserted into entry */ +static void +insert_text_handler (GtkEditable *editable, + const gchar *text, + gint length, + gint *position, + GladeNamedIconChooserDialog *dialog) +{ + if (is_well_formed (text)) + { + + g_signal_handlers_block_by_func (editable, (gpointer) insert_text_handler, + dialog); + + gtk_editable_insert_text (editable, text, length, position); + + g_signal_handlers_unblock_by_func (editable, + (gpointer) insert_text_handler, + dialog); + + } + else + { + gdk_display_beep (gtk_widget_get_display (GTK_WIDGET (dialog))); + } + + g_signal_stop_emission_by_name (editable, "insert-text"); +} + +typedef struct +{ + gchar *name; /* the name of the icon or context */ + + guint found:1; /* whether an item matching `name' was found */ + guint do_select:1; /* select the matched row */ + guint do_cursor:1; /* put cursor at the matched row */ + guint do_activate:1; /* activate the matched row */ + + GladeNamedIconChooserDialog *dialog; +} ForEachFuncData; + +void +glade_named_icon_chooser_dialog_set_icon_name (GladeNamedIconChooserDialog *dialog, + const gchar *name) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + ForEachFuncData *data; + gboolean located_in_theme; + + g_return_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog)); + g_return_if_fail (gtk_widget_has_screen (GTK_WIDGET (dialog))); + + if (name == NULL) + { + gtk_tree_selection_unselect_all (priv->selection); + entry_set_name (dialog, ""); + return; + } + + located_in_theme = + gtk_icon_theme_has_icon (get_icon_theme_for_widget (GTK_WIDGET (dialog)), + name); + + if (located_in_theme) + { + + if (priv->icons_loaded && priv->filter_model) + { + + data = g_slice_new0 (ForEachFuncData); + data->name = g_strdup (name); + data->found = FALSE; + data->do_activate = FALSE; + data->do_select = TRUE; + data->do_cursor = TRUE; + data->dialog = dialog; + + gtk_tree_model_foreach (priv->filter_model, + scan_for_name_func, data); + + g_free (data->name); + g_slice_free (ForEachFuncData, data); + + } + else + { + priv->pending_select_name = g_strdup (name); + } + + /* selecting a treeview row will set the entry text, + * but we must have this here in case the row has been filtered out + */ + entry_set_name (dialog, name); + + } + else if (is_well_formed (name)) + { + + gtk_tree_selection_unselect_all (priv->selection); + + entry_set_name (dialog, name); + } + else + { + g_warning ("invalid icon name: '%s' is not well formed", name); + } +} + +gboolean +glade_named_icon_chooser_dialog_set_context (GladeNamedIconChooserDialog *dialog, + const gchar *name) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + ForEachFuncData *data; + + g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), FALSE); + + data = g_slice_new0 (ForEachFuncData); + + if (name) + data->name = g_strdup (name); + else + data->name = g_strdup ("All Contexts"); + + data->found = FALSE; + data->do_select = TRUE; + data->do_activate = FALSE; + data->do_cursor = FALSE; + data->dialog = dialog; + + gtk_tree_model_foreach (GTK_TREE_MODEL (priv->contexts_store), + (GtkTreeModelForeachFunc) scan_for_context_func, + data); + + g_free (data->name); + g_slice_free (ForEachFuncData, data); + + return TRUE; +} + +gchar * +glade_named_icon_chooser_dialog_get_context (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkTreeSelection *sel; + GtkTreeIter iter; + gchar *context_name; + + g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), NULL); + + sel = + gtk_tree_view_get_selection (GTK_TREE_VIEW (priv->contexts_view)); + + if (gtk_tree_selection_get_selected (sel, NULL, &iter)) + { + + gtk_tree_model_get (GTK_TREE_MODEL (priv->contexts_store), &iter, + CONTEXTS_NAME_COLUMN, &context_name, -1); + + /* if context_name is NULL, then it is the 'all categories' special context */ + return context_name; + + } + else + { + return NULL; + } +} + +static gchar * +get_icon_name_from_selection (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkTreeIter iter; + GtkTreeModel *model; + gchar *name; + + if (!gtk_tree_selection_get_selected (priv->selection, &model, &iter)) + return NULL; + + gtk_tree_model_get (model, &iter, ICONS_NAME_COLUMN, &name, -1); + + return name; +} + +gchar * +glade_named_icon_chooser_dialog_get_icon_name (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkWidget *current_focus; + gchar *name; + + g_return_val_if_fail (GLADE_IS_NAMED_ICON_CHOOSER_DIALOG (dialog), NULL); + + current_focus = gtk_window_get_focus (GTK_WINDOW (dialog)); + + if (current_focus == priv->icons_view) + { + + view: + name = get_icon_name_from_selection (dialog); + + if (name == NULL) + goto entry; + + } + else if (current_focus == priv->entry) + { + gboolean is_wellformed, is_empty; + entry: + check_entry_text (dialog, &name, &is_wellformed, &is_empty); + + if (!is_wellformed || is_empty) + return NULL; + + } + else if (priv->last_focus_widget == priv->icons_view) + { + goto view; + } + else if (priv->last_focus_widget == priv->entry) + { + goto entry; + } + else + { + goto view; + } + + return name; +} + +static void +set_busy_cursor (GladeNamedIconChooserDialog *dialog, gboolean busy) +{ + GdkDisplay *display; + GdkCursor *cursor; + + if (!gtk_widget_get_realized (GTK_WIDGET (dialog))) + return; + + display = gtk_widget_get_display (GTK_WIDGET (dialog)); + + if (busy) + cursor = gdk_cursor_new_for_display (display, GDK_WATCH); + else + cursor = NULL; + + gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (dialog)), cursor); + gdk_display_flush (display); + + if (cursor) + g_object_unref (cursor); +} + +static GtkListStore * +populate_icon_contexts_model (void) +{ + GtkListStore *store; + GtkTreeIter iter; + guint i; + + store = gtk_list_store_new (CONTEXTS_N_COLUMS, + G_TYPE_INT, G_TYPE_STRING, G_TYPE_STRING); + + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + CONTEXTS_ID_COLUMN, -1, + CONTEXTS_NAME_COLUMN, "All Contexts", + CONTEXTS_TITLE_COLUMN, _("All Contexts"), -1); + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + CONTEXTS_ID_COLUMN, -1, + CONTEXTS_NAME_COLUMN, NULL, + CONTEXTS_TITLE_COLUMN, NULL, -1); + + for (i = 0; i < G_N_ELEMENTS (standard_contexts); i++) + { + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + CONTEXTS_ID_COLUMN, i, + CONTEXTS_NAME_COLUMN, standard_contexts[i].name, + CONTEXTS_TITLE_COLUMN, _(standard_contexts[i].title), + -1); + } + + return store; +} + +static void +icons_row_activated_cb (GtkTreeView *view, + GtkTreePath *path, + GtkTreeViewColumn *column, + GladeNamedIconChooserDialog *dialog) +{ + g_signal_emit_by_name (dialog, "icon-activated", NULL); +} + +static void +icons_selection_changed_cb (GtkTreeSelection * selection, + GladeNamedIconChooserDialog * dialog) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gchar *name; + + if (gtk_tree_selection_get_selected (selection, &model, &iter)) + { + gtk_tree_model_get (model, &iter, ICONS_NAME_COLUMN, &name, -1); + if (name) + entry_set_name (dialog, name); + + g_free (name); + } + else + { + /* entry_set_name (dialog, ""); */ + } + + /* we emit "selection-changed" for chooser in insert_text_handler() + * to avoid emitting the signal twice */ +} + +static void +contexts_row_activated_cb (GtkTreeView *view, + GtkTreePath *cpath, + GtkTreeViewColumn *column, + GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkTreeIter iter; + GtkTreePath *path; + + if (gtk_tree_model_get_iter_first (priv->filter_model, &iter)) + { + + gtk_tree_selection_select_iter (priv->selection, &iter); + + path = gtk_tree_model_get_path (priv->filter_model, &iter); + + gtk_tree_selection_select_path (priv->selection, path); + + gtk_tree_view_scroll_to_point (GTK_TREE_VIEW (priv->icons_view), + -1, 0); + + gtk_tree_path_free (path); + + } + gtk_widget_grab_focus (priv->icons_view); +} + +static void +contexts_selection_changed_cb (GtkTreeSelection *selection, + GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkTreeIter iter; + GtkTreeModel *model; + gboolean retval; + gint context_id; + + retval = gtk_tree_selection_get_selected (selection, &model, &iter); + + if (retval) + { + + gtk_tree_model_get (model, &iter, CONTEXTS_ID_COLUMN, &context_id, -1); + + priv->context_id = context_id; + + if (!priv->filter_model) + return; + + filter_icons_model (dialog); + } + + entry_set_name (dialog, ""); + +} + +static gboolean +row_separator_func (GtkTreeModel *model, GtkTreeIter *iter, gpointer unused) +{ + gboolean retval; + gchar *name, *title; + + gtk_tree_model_get (model, iter, + CONTEXTS_NAME_COLUMN, &name, + CONTEXTS_TITLE_COLUMN, &title, -1); + + retval = !name && !title; + + g_free (name); + g_free (title); + + return retval; +} + +static GtkWidget * +create_contexts_view (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkTreeView *view; + GtkTreeViewColumn *column; + GtkTreePath *path; + + priv->contexts_store = populate_icon_contexts_model (); + + view = + GTK_TREE_VIEW (gtk_tree_view_new_with_model + (GTK_TREE_MODEL (priv->contexts_store))); + + column = gtk_tree_view_column_new_with_attributes (NULL, + gtk_cell_renderer_text_new + (), "text", + CONTEXTS_TITLE_COLUMN, + NULL); + + gtk_tree_view_append_column (view, column); + gtk_tree_view_set_headers_visible (view, FALSE); + + gtk_tree_view_set_row_separator_func (view, + (GtkTreeViewRowSeparatorFunc) + row_separator_func, NULL, NULL); + + gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view), + GTK_SELECTION_BROWSE); + + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_selection_select_path (gtk_tree_view_get_selection (view), path); + gtk_tree_path_free (path); + + g_signal_connect (view, "row-activated", + G_CALLBACK (contexts_row_activated_cb), dialog); + + g_signal_connect (gtk_tree_view_get_selection (view), "changed", + G_CALLBACK (contexts_selection_changed_cb), dialog); + + gtk_widget_show (GTK_WIDGET (view)); + + return GTK_WIDGET (view); +} + +/* filters the icons model based on the current state */ +static void +filter_icons_model (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + + set_busy_cursor (dialog, TRUE); + + g_object_ref (priv->filter_model); + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->icons_view), NULL); + gtk_entry_completion_set_model (priv->entry_completion, NULL); + + gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER + (priv->filter_model)); + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->icons_view), + priv->filter_model); + gtk_entry_completion_set_model (priv->entry_completion, + GTK_TREE_MODEL (priv->icons_store)); + gtk_entry_completion_set_text_column (priv->entry_completion, + ICONS_NAME_COLUMN); + g_object_unref (priv->filter_model); + + set_busy_cursor (dialog, FALSE); +} + +static gboolean +filter_visible_func (GtkTreeModel *model, + GtkTreeIter *iter, + GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + gboolean standard; + gint context_id; + + gtk_tree_model_get (model, iter, + ICONS_CONTEXT_COLUMN, &context_id, + ICONS_STANDARD_COLUMN, &standard, -1); + + if (priv->context_id == -1) + return (priv->settings_list_standard) ? TRUE && standard : TRUE; + + if (context_id == priv->context_id) + return (priv->settings_list_standard) ? TRUE && standard : TRUE; + else + return FALSE; +} + + +static gboolean +search_equal_func (GtkTreeModel *model, + gint column, + const gchar *key, + GtkTreeIter *iter, + GladeNamedIconChooserDialog *dialog) +{ + gchar *name; + gboolean retval; + + gtk_tree_model_get (model, iter, ICONS_NAME_COLUMN, &name, -1); + + retval = !g_str_has_prefix (name, key); + + g_free (name); + + return retval; + +} + +static gboolean +scan_for_context_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + ForEachFuncData *data = (ForEachFuncData *) user_data; + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (data->dialog); + GtkTreeSelection *selection = + gtk_tree_view_get_selection (GTK_TREE_VIEW + (priv->contexts_view)); + gchar *name = NULL; + + gtk_tree_model_get (model, iter, CONTEXTS_NAME_COLUMN, &name, -1); + if (!name) + return FALSE; + + if (strcmp (name, data->name) == 0) + { + + data->found = TRUE; + + if (data->do_activate) + gtk_tree_view_row_activated (GTK_TREE_VIEW + (priv->contexts_view), path, + gtk_tree_view_get_column (GTK_TREE_VIEW + (priv-> + contexts_view), + 0)); + + if (data->do_select) + gtk_tree_selection_select_path (selection, path); + else + gtk_tree_selection_unselect_path (selection, path); + + if (data->do_cursor) + gtk_tree_view_set_cursor (GTK_TREE_VIEW + (priv->contexts_view), path, + NULL, FALSE); + + g_free (name); + + return TRUE; + } + + g_free (name); + + return FALSE; +} + +gboolean +scan_for_name_func (GtkTreeModel *model, + GtkTreePath *path, + GtkTreeIter *iter, + gpointer user_data) +{ + ForEachFuncData *data = (ForEachFuncData *) user_data; + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (data->dialog); + gchar *name = NULL; + + gtk_tree_model_get (model, iter, ICONS_NAME_COLUMN, &name, -1); + if (!name) + return FALSE; + + if (strcmp (name, data->name) == 0) + { + + data->found = TRUE; + + if (data->do_activate) + gtk_tree_view_row_activated (GTK_TREE_VIEW + (priv->icons_view), path, + gtk_tree_view_get_column (GTK_TREE_VIEW + (priv-> + icons_view), + 0)); + + if (data->do_select) + gtk_tree_selection_select_path (priv->selection, path); + else + gtk_tree_selection_unselect_path (priv->selection, path); + + if (data->do_cursor) + gtk_tree_view_set_cursor (GTK_TREE_VIEW + (priv->icons_view), path, NULL, + FALSE); + + g_free (name); + + return TRUE; + } + + g_free (name); + + return FALSE; +} + +static void +centre_selected_row (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GList *l; + + g_assert (priv->icons_store != NULL); + g_assert (priv->selection != NULL); + + l = gtk_tree_selection_get_selected_rows (priv->selection, NULL); + + if (l) + { + g_assert (gtk_widget_get_mapped (GTK_WIDGET (dialog))); + g_assert (gtk_widget_get_visible (GTK_WIDGET (dialog))); + + gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (priv->icons_view), + (GtkTreePath *) l->data, + NULL, TRUE, 0.5, 0.0); + +/* gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->icons_view), + (GtkTreePath *) l->data, + 0, + FALSE); + + gtk_widget_grab_focus (priv->icons_view); +*/ + g_list_free_full (l, (GDestroyNotify) gtk_tree_path_free); + } +} + +static void +select_first_row (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkTreePath *path; + + if (!priv->filter_model) + return; + + path = gtk_tree_path_new_from_indices (0, -1); + gtk_tree_view_set_cursor (GTK_TREE_VIEW (priv->icons_view), path, + NULL, FALSE); + gtk_tree_path_free (path); +} + +static void +pending_select_name_process (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + ForEachFuncData *data; + + g_assert (priv->icons_store != NULL); + g_assert (priv->selection != NULL); + + if (priv->pending_select_name) + { + + data = g_slice_new0 (ForEachFuncData); + + data->name = priv->pending_select_name; + data->do_select = TRUE; + data->do_activate = FALSE; + data->dialog = dialog; + + gtk_tree_model_foreach (priv->filter_model, + scan_for_name_func, data); + + g_free (priv->pending_select_name); + priv->pending_select_name = NULL; + + g_slice_free (ForEachFuncData, data); + + } + else + { + if (strlen (gtk_entry_get_text (GTK_ENTRY (priv->entry))) == 0) + { + select_first_row (dialog); + } + } + + centre_selected_row (dialog); +} + +static gboolean +is_standard_icon_name (const gchar *icon_name) +{ + GQuark quark; + + quark = g_quark_try_string (icon_name); + + if (quark == 0) + return FALSE; + + return (g_hash_table_lookup (standard_icon_quarks, GUINT_TO_POINTER (quark)) + != NULL); + +} + +static void +cleanup_after_load (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + priv->load_id = 0; + + pending_select_name_process (dialog); + + set_busy_cursor (dialog, FALSE); +} + +static void +chooser_set_model (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + + /* filter model */ + priv->filter_model = + gtk_tree_model_filter_new (GTK_TREE_MODEL (priv->icons_store), + NULL); + + gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER + (priv->filter_model), + (GtkTreeModelFilterVisibleFunc) + filter_visible_func, dialog, NULL); + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->icons_view), + priv->filter_model); + g_object_unref (priv->filter_model); + + gtk_entry_completion_set_model (priv->entry_completion, + GTK_TREE_MODEL (priv->icons_store)); + gtk_entry_completion_set_text_column (priv->entry_completion, + ICONS_NAME_COLUMN); + + gtk_tree_view_set_search_column (GTK_TREE_VIEW (priv->icons_view), + ICONS_NAME_COLUMN); + + priv->icons_loaded = TRUE; +} + +typedef struct +{ + gchar *name; + gint context; +} IconData; + +static gint +icon_data_compare (IconData *a, IconData *b) +{ + return g_ascii_strcasecmp (a->name, b->name); +} + +static gboolean +reload_icons (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkListStore *store = priv->icons_store; + GtkTreeIter iter; + guint i; + GList *l, *icons = NULL; + + /* retrieve icon names from each context */ + for (i = 0; i < G_N_ELEMENTS (standard_contexts); i++) + { + + GList *icons_in_context = + gtk_icon_theme_list_icons (priv->icon_theme, + standard_contexts[i].name); + + for (l = icons_in_context; l; l = l->next) + { + + IconData *data = g_slice_new (IconData); + + data->name = (gchar *) l->data; + data->context = i; + + icons = g_list_prepend (icons, data); + } + + g_list_free (icons_in_context); + } + + /* sort icon names */ + icons = g_list_sort (icons, (GCompareFunc) icon_data_compare); + + /* put into to model */ + for (l = icons; l; l = l->next) + { + + IconData *data = (IconData *) l->data; + + gtk_list_store_append (store, &iter); + gtk_list_store_set (store, &iter, + ICONS_CONTEXT_COLUMN, data->context, + ICONS_STANDARD_COLUMN, + is_standard_icon_name (data->name), ICONS_NAME_COLUMN, + data->name, -1); + + g_free (data->name); + g_slice_free (IconData, data); + } + + g_list_free (icons); + + chooser_set_model (dialog); + + return FALSE; +} + +static void +change_icon_theme (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + if (priv->icon_theme == NULL) + priv->icon_theme = get_icon_theme_for_widget (GTK_WIDGET (dialog)); + + gtk_tree_view_set_model (GTK_TREE_VIEW (priv->icons_view), NULL); + gtk_list_store_clear (priv->icons_store); + + set_busy_cursor (dialog, TRUE); + + priv->load_id = g_idle_add_full (G_PRIORITY_HIGH_IDLE + 300, + (GSourceFunc) reload_icons, + dialog, + (GDestroyNotify) cleanup_after_load); + +} + +static void +glade_named_icon_chooser_dialog_screen_changed (GtkWidget *widget, + GdkScreen *previous_screen) +{ + GladeNamedIconChooserDialog *dialog; + + dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); + + if (GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> + screen_changed) + GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> + screen_changed (widget, previous_screen); + + if (gtk_widget_get_mapped (widget)) + change_icon_theme (dialog); + +} + +static GtkWidget * +create_icons_view (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkTreeView *view; + GtkTreeViewColumn *column; + GtkCellRenderer *pixbuf_renderer, *text_renderer; + + view = GTK_TREE_VIEW (gtk_tree_view_new ()); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_min_width (column, 56); + gtk_tree_view_column_set_title (column, NULL); + pixbuf_renderer = gtk_cell_renderer_pixbuf_new (); + + gtk_tree_view_column_pack_start (column, pixbuf_renderer, TRUE); + + gtk_tree_view_column_set_attributes (column, + pixbuf_renderer, + "icon-name", ICONS_NAME_COLUMN, NULL); + + gtk_tree_view_append_column (view, column); + g_object_set (pixbuf_renderer, + "xpad", 2, + "xalign", 1.0, "stock-size", GTK_ICON_SIZE_MENU, NULL); + + column = gtk_tree_view_column_new (); + gtk_tree_view_column_set_title (column, "Name"); + text_renderer = gtk_cell_renderer_text_new (); + g_object_set (G_OBJECT (text_renderer), + "ellipsize", PANGO_ELLIPSIZE_END, "yalign", 0.0, NULL); + + gtk_tree_view_column_pack_start (column, text_renderer, TRUE); + + gtk_tree_view_column_set_attributes (column, + text_renderer, + "text", ICONS_NAME_COLUMN, NULL); + + + gtk_tree_view_append_column (view, column); + gtk_tree_view_column_set_expand (column, TRUE); + gtk_tree_view_column_set_resizable (column, FALSE); + + gtk_tree_view_set_headers_visible (view, FALSE); + + gtk_tree_view_set_enable_search (view, TRUE); + gtk_tree_view_set_search_equal_func (view, + (GtkTreeViewSearchEqualFunc) + search_equal_func, dialog, NULL); + + g_signal_connect (view, "row-activated", + G_CALLBACK (icons_row_activated_cb), dialog); + + g_signal_connect (gtk_tree_view_get_selection (view), "changed", + G_CALLBACK (icons_selection_changed_cb), dialog); + + gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view), + GTK_SELECTION_BROWSE); + + priv->selection = gtk_tree_view_get_selection (view); + + gtk_tree_view_set_rules_hint (view, TRUE); + + gtk_widget_show (GTK_WIDGET (view)); + + return GTK_WIDGET (view); +} + +/* sets the 'list-standard' state and refilters the icons model */ +static void +button_toggled (GtkToggleButton *button, GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + priv->settings_list_standard = gtk_toggle_button_get_active (button); + + if (priv->filter_model != NULL) + filter_icons_model (dialog); +} + +static GHashTable * +create_standard_icon_quarks (void) +{ + GHashTable *table; + GQuark quark; + guint i; + + table = g_hash_table_new (NULL, NULL); + + for (i = 0; i < G_N_ELEMENTS (standard_icon_names); i++) + { + + quark = g_quark_from_static_string (standard_icon_names[i]); + + g_hash_table_insert (table, + GUINT_TO_POINTER (quark), GUINT_TO_POINTER (quark)); + } + + return table; +} + +static void +glade_named_icon_chooser_dialog_style_set (GtkWidget *widget, + GtkStyle *previous_style) +{ + if (gtk_widget_has_screen (widget) && gtk_widget_get_mapped (widget)) + change_icon_theme (GLADE_NAMED_ICON_CHOOSER_DIALOG (widget)); +} + +/* override GtkWidget::show_all since we have internal widgets we wish to keep + * hidden unless we decide otherwise, like the list-standard-icons-only checkbox. + */ +static void +glade_named_icon_chooser_dialog_show_all (GtkWidget *widget) +{ + gtk_widget_show (widget); +} + +/* Handler for GtkWindow::set-focus; this is where we save the last-focused + * widget on our toplevel. See glade_named_icon_chooser_dialog_hierarchy_changed() + */ +static void +glade_named_icon_chooser_dialog_set_focus (GtkWindow *window, GtkWidget *focus) +{ + GladeNamedIconChooserDialog *dialog = GLADE_NAMED_ICON_CHOOSER_DIALOG (window); + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + + GTK_WINDOW_CLASS (glade_named_icon_chooser_dialog_parent_class)-> + set_focus (window, focus); + + priv->last_focus_widget = gtk_window_get_focus (window); +} + +static void +glade_named_icon_chooser_dialog_finalize (GObject *object) +{ + GladeNamedIconChooserDialog *dialog = + GLADE_NAMED_ICON_CHOOSER_DIALOG (object); + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + + g_clear_pointer (&priv->pending_select_name, g_free); + + G_OBJECT_CLASS (glade_named_icon_chooser_dialog_parent_class)-> + finalize (object); +} + +static void +glade_named_icon_chooser_dialog_map (GtkWidget *widget) +{ + GladeNamedIconChooserDialog *dialog = + GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + + GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)->map (widget); + + settings_load (dialog); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->button), + priv->settings_list_standard); + + gtk_widget_grab_focus (priv->icons_view); +} + +static void +glade_named_icon_chooser_dialog_unmap (GtkWidget *widget) +{ + GladeNamedIconChooserDialog *dialog = + GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); + + settings_save (dialog); + + GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> + unmap (widget); +} + +/* we load the icons in expose() because we want the widget + * to be fully painted before loading begins + */ +static gboolean +glade_named_icon_chooser_dialog_draw (GtkWidget *widget, cairo_t *cr) +{ + GladeNamedIconChooserDialog *dialog = + GLADE_NAMED_ICON_CHOOSER_DIALOG (widget); + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + gboolean retval; + + retval = + GTK_WIDGET_CLASS (glade_named_icon_chooser_dialog_parent_class)-> + draw (widget, cr); + if (!priv->icons_loaded) + { + change_icon_theme (GLADE_NAMED_ICON_CHOOSER_DIALOG (widget)); + priv->icons_loaded = TRUE; + } + + return retval; +} + +static void +response_cb (GtkDialog *dialog, gint response_id) +{ + /* Act only on response IDs we recognize */ + if (!(response_id == GTK_RESPONSE_ACCEPT + || response_id == GTK_RESPONSE_OK + || response_id == GTK_RESPONSE_YES + || response_id == GTK_RESPONSE_APPLY)) + return; + + if (!should_respond (GLADE_NAMED_ICON_CHOOSER_DIALOG (dialog))) + { + g_signal_stop_emission_by_name (dialog, "response"); + } +} + +/* we intercept the GladeNamedIconChooser::icon-activated signal and try to + * make the dialog emit a valid response signal + */ +static void +icon_activated_cb (GladeNamedIconChooserDialog *dialog) +{ + GList *children, *l; + + children = + gtk_container_get_children (GTK_CONTAINER + (gtk_dialog_get_action_area + (GTK_DIALOG (dialog)))); + + for (l = children; l; l = l->next) + { + GtkWidget *widget; + gint response_id; + + widget = GTK_WIDGET (l->data); + response_id = + gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget); + + if (response_id == GTK_RESPONSE_ACCEPT || + response_id == GTK_RESPONSE_OK || + response_id == GTK_RESPONSE_YES || response_id == GTK_RESPONSE_APPLY) + { + g_list_free (children); + + gtk_dialog_response (GTK_DIALOG (dialog), response_id); + + return; + } + } + g_list_free (children); +} + +/* we intercept the GladeNamedIconChooser::selection-changed signal and try to + * make the affirmative response button insensitive when the selection is empty + */ +static void +selection_changed_cb (GladeNamedIconChooserDialog *dialog) +{ + GList *children, *l; + gchar *icon_name; + + children = + gtk_container_get_children (GTK_CONTAINER + (gtk_dialog_get_action_area + (GTK_DIALOG (dialog)))); + + for (l = children; l; l = l->next) + { + GtkWidget *widget; + gint response_id; + + widget = GTK_WIDGET (l->data); + response_id = + gtk_dialog_get_response_for_widget (GTK_DIALOG (dialog), widget); + + if (response_id == GTK_RESPONSE_ACCEPT || + response_id == GTK_RESPONSE_OK || + response_id == GTK_RESPONSE_YES || response_id == GTK_RESPONSE_APPLY) + { + icon_name = glade_named_icon_chooser_dialog_get_icon_name (dialog); + + gtk_dialog_set_response_sensitive (GTK_DIALOG (dialog), + response_id, icon_name != NULL); + g_free (icon_name); + g_list_free (children); + return; + } + } + g_list_free (children); +} + +static void +glade_named_icon_chooser_dialog_init (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GtkWidget *contents; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *sw; + GtkWidget *label; + GtkWidget *hpaned; + GtkWidget *content_area; + GtkSizeGroup *group; + + priv->filter_model = NULL; + priv->icons_store = NULL; + priv->context_id = -1; + priv->pending_select_name = NULL; + priv->last_focus_widget = NULL; + priv->icons_loaded = FALSE; + + + gtk_window_set_title (GTK_WINDOW (dialog), _("Named Icon Chooser")); + + gtk_window_set_default_size (GTK_WINDOW (dialog), 610, 480); + + _glade_util_dialog_set_hig (GTK_DIALOG (dialog)); + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + /* We do a signal connection here rather than overriding the method in + * class_init because GtkDialog::response is a RUN_LAST signal. We want *our* + * handler to be run *first*, regardless of whether the user installs response + * handlers of his own. + */ + g_signal_connect (dialog, "response", G_CALLBACK (response_cb), NULL); + + g_signal_connect (dialog, "icon-activated", + G_CALLBACK (icon_activated_cb), NULL); + + g_signal_connect (dialog, "selection-changed", + G_CALLBACK (selection_changed_cb), NULL); + + + if (standard_icon_quarks == NULL) + standard_icon_quarks = create_standard_icon_quarks (); + + contents = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_container_set_border_width (GTK_CONTAINER (contents), 5); + gtk_widget_show (contents); + + label = gtk_label_new_with_mnemonic (_("Icon _Name:")); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_widget_show (label); + + priv->entry = gtk_entry_new (); + gtk_entry_set_activates_default (GTK_ENTRY (priv->entry), TRUE); + gtk_entry_set_width_chars (GTK_ENTRY (priv->entry), 40); + g_object_set (G_OBJECT (priv->entry), "truncate-multiline", TRUE, + NULL); + g_signal_connect (G_OBJECT (priv->entry), "changed", + G_CALLBACK (changed_text_handler), dialog); + g_signal_connect (G_OBJECT (priv->entry), "insert-text", + G_CALLBACK (insert_text_handler), dialog); + gtk_widget_show (priv->entry); + + priv->entry_completion = gtk_entry_completion_new (); + gtk_entry_set_completion (GTK_ENTRY (priv->entry), + priv->entry_completion); + gtk_entry_completion_set_popup_completion (priv->entry_completion, + FALSE); + gtk_entry_completion_set_inline_completion (priv->entry_completion, + TRUE); + + gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->entry); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_widget_show (hbox); + + gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), priv->entry, TRUE, TRUE, 0); + gtk_box_pack_start (GTK_BOX (contents), hbox, FALSE, FALSE, 6); + + hpaned = gtk_paned_new (GTK_ORIENTATION_HORIZONTAL); + gtk_paned_set_position (GTK_PANED (hpaned), 150); + gtk_widget_show (hpaned); + + priv->contexts_view = create_contexts_view (dialog); + priv->icons_view = create_icons_view (dialog); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_show (vbox); + + group = gtk_size_group_new (GTK_SIZE_GROUP_VERTICAL); + + label = gtk_label_new_with_mnemonic (_("C_ontexts:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), + priv->contexts_view); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_size_group_add_widget (group, label); + gtk_widget_show (label); + + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + gtk_widget_show (sw); + + gtk_container_add (GTK_CONTAINER (sw), priv->contexts_view); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_paned_pack1 (GTK_PANED (hpaned), vbox, FALSE, FALSE); + + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_widget_show (vbox); + + label = gtk_label_new_with_mnemonic (_("Icon Na_mes:")); + gtk_label_set_mnemonic_widget (GTK_LABEL (label), priv->icons_view); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_size_group_add_widget (group, label); + gtk_widget_show (label); + + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_NEVER, + GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_IN); + gtk_widget_show (sw); + + gtk_container_add (GTK_CONTAINER (sw), priv->icons_view); + gtk_box_pack_start (GTK_BOX (vbox), sw, TRUE, TRUE, 0); + gtk_paned_pack2 (GTK_PANED (hpaned), vbox, TRUE, FALSE); + + gtk_box_pack_start (GTK_BOX (contents), hpaned, TRUE, TRUE, 0); + + + g_object_unref (G_OBJECT (group)); + + priv->button = + gtk_check_button_new_with_mnemonic (_("_List standard icons only")); + gtk_widget_show (priv->button); + + g_signal_connect (priv->button, "toggled", + G_CALLBACK (button_toggled), dialog); + + gtk_box_pack_start (GTK_BOX (contents), priv->button, FALSE, FALSE, + 0); + gtk_box_pack_start (GTK_BOX (content_area), contents, TRUE, TRUE, 0); + + /* underlying model */ + priv->icons_store = gtk_list_store_new (ICONS_N_COLUMNS, + G_TYPE_UINT, + G_TYPE_BOOLEAN, + G_TYPE_STRING); +} + +static void +glade_named_icon_chooser_dialog_class_init (GladeNamedIconChooserDialogClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + GtkWindowClass *window_class; + + object_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + window_class = GTK_WINDOW_CLASS (klass); + + object_class->finalize = glade_named_icon_chooser_dialog_finalize; + + widget_class->map = glade_named_icon_chooser_dialog_map; + widget_class->unmap = glade_named_icon_chooser_dialog_unmap; + widget_class->draw = glade_named_icon_chooser_dialog_draw; + widget_class->show_all = glade_named_icon_chooser_dialog_show_all; + widget_class->style_set = glade_named_icon_chooser_dialog_style_set; + widget_class->screen_changed = glade_named_icon_chooser_dialog_screen_changed; + + window_class->set_focus = glade_named_icon_chooser_dialog_set_focus; + + /** + * GladeNamedIconChooserDialog::icon-activated + * @chooser: the object which received the signal + * + * This signal is emitted when the user "activates" an icon + * in the named icon chooser. This can happen by double-clicking on an item + * in the recently used resources list, or by pressing + * Enter. + */ + dialog_signals[ICON_ACTIVATED] = + g_signal_new ("icon-activated", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeNamedIconChooserDialogClass, + icon_activated), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GladeNamedIconChooserDialog::selection-changed + * @chooser: the object which received the signal + * + * This signal is emitted when there is a change in the set of + * selected icon names. This can happen when a user + * modifies the selection with the mouse or the keyboard, or when + * explicitly calling functions to change the selection. + */ + dialog_signals[SELECTION_CHANGED] = + g_signal_new ("selection-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeNamedIconChooserDialogClass, + selection_changed), NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +static gboolean +should_respond (GladeNamedIconChooserDialog *dialog) +{ + gchar *icon_name; + + /* is there an icon selected? */ + icon_name = glade_named_icon_chooser_dialog_get_icon_name (dialog); + if (!icon_name) + return FALSE; + + g_free (icon_name); + return TRUE; +} + +/* get's the name of the configuration file */ +static gchar * +get_config_filename (void) +{ + return g_build_filename (g_get_user_config_dir (), "gladeui", "config", NULL); +} + +/* get's the name of the directory that contains the config file */ +static char * +get_config_dirname (void) +{ + return g_build_filename (g_get_user_config_dir (), "gladeui", NULL); +} + +/* loads the configuration settings */ +static void +settings_load (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GKeyFile *keyfile; + gboolean success, boolean_value; + gchar *filename; + GError *error = NULL; + + keyfile = g_key_file_new (); + + filename = get_config_filename (); + success = g_key_file_load_from_file (keyfile, + filename, G_KEY_FILE_NONE, &error); + g_free (filename); + + if (!success) + { + + priv->settings_list_standard = DEFAULT_SETTING_LIST_STANDARD_ONLY; + + g_clear_error (&error); + g_key_file_free (keyfile); + return; + } + + + boolean_value = g_key_file_get_boolean (keyfile, + "Named Icon Chooser", + "ListStandardOnly", &error); + if (error) + { + priv->settings_list_standard = DEFAULT_SETTING_LIST_STANDARD_ONLY; + g_clear_error (&error); + } + else + { + priv->settings_list_standard = boolean_value; + } + + g_key_file_free (keyfile); +} + +/* creates a GKeyFile based on the current settings */ +static GKeyFile * +settings_to_keyfile (GladeNamedIconChooserDialog *dialog) +{ + GladeNamedIconChooserDialogPrivate *priv = glade_named_icon_chooser_dialog_get_instance_private (dialog); + GKeyFile *keyfile; + gchar *filename; + + keyfile = g_key_file_new (); + + filename = get_config_filename (); + g_key_file_load_from_file (keyfile, + get_config_filename (), + G_KEY_FILE_NONE, NULL); + g_free (filename); + + g_key_file_set_boolean (keyfile, + "Named Icon Chooser", + "ListStandardOnly", + priv->settings_list_standard); + + return keyfile; +} + +/* serializes the current configuration to the config file */ +static void +settings_save (GladeNamedIconChooserDialog *dialog) +{ + GKeyFile *keyfile; + gchar *contents; + gsize contents_length; + gchar *filename = NULL, *dirname = NULL; + GError *error = NULL; + + keyfile = settings_to_keyfile (dialog); + + contents = g_key_file_to_data (keyfile, &contents_length, &error); + + if (error) + goto out; + + filename = get_config_filename (); + + if (!g_file_set_contents (filename, contents, contents_length, NULL)) + { + gchar *dirname; + gint saved_errno; + + dirname = get_config_dirname (); + if (g_mkdir_with_parents (dirname, 0700) != 0) /* 0700 per the XDG basedir spec */ + { + + saved_errno = errno; + g_set_error (&error, + G_FILE_ERROR, + g_file_error_from_errno (saved_errno), + _("Could not create directory: %s"), dirname); + goto out; + } + + if (!g_file_set_contents (filename, contents, contents_length, &error)) + { + goto out; + } + } + +out: + + g_free (contents); + g_free (dirname); + g_free (filename); + g_clear_error (&error); + g_key_file_free (keyfile); +} + +static GtkWidget * +glade_named_icon_chooser_dialog_new_valist (const gchar *title, + GtkWindow *parent, + const gchar *first_button_text, + va_list varargs) +{ + GtkWidget *result; + const char *button_text = first_button_text; + gint response_id; + + result = g_object_new (GLADE_TYPE_NAMED_ICON_CHOOSER_DIALOG, + "title", title, "transient-for", parent, NULL); + + while (button_text) + { + response_id = va_arg (varargs, gint); + gtk_dialog_add_button (GTK_DIALOG (result), button_text, response_id); + button_text = va_arg (varargs, const gchar *); + } + + return result; +} + +/** + * glade_named_icon_chooser_dialog_new: + * @title: (nullable): Title of the dialog, or %NULL + * @parent: (nullable): Transient parent of the dialog, or %NULL, + * @first_button_text: (nullable): stock ID or text to go in the first button, or %NULL + * @...: response ID for the first button, then additional (button, id) + * pairs, ending with %NULL + * + * Creates a new #GladeNamedIconChooserDialog. This function is analogous to + * gtk_dialog_new_with_buttons(). + * + * Return value: a new #GladeNamedIconChooserDialog + */ +GtkWidget * +glade_named_icon_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + const gchar *first_button_text, + ...) +{ + GtkWidget *result; + va_list varargs; + + va_start (varargs, first_button_text); + result = glade_named_icon_chooser_dialog_new_valist (title, + parent, + first_button_text, + varargs); + va_end (varargs); + + return result; +} diff --git a/gladeui/glade-named-icon-chooser-dialog.h b/gladeui/glade-named-icon-chooser-dialog.h new file mode 100644 index 0000000..f42db34 --- /dev/null +++ b/gladeui/glade-named-icon-chooser-dialog.h @@ -0,0 +1,61 @@ +/* + * glade-named-icon-chooser-dialog.h - Named icon chooser dialog + * + * Copyright (C) 2007 Vincent Geddes + * + * Author: Vincent Geddes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANNAMED_ICON_CHOOSERILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef __GLADE_NAMED_ICON_CHOOSER_DIALOG_H__ +#define __GLADE_NAMED_ICON_CHOOSER_DIALOG_H__ + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_NAMED_ICON_CHOOSER_DIALOG glade_named_icon_chooser_dialog_get_type () +G_DECLARE_DERIVABLE_TYPE (GladeNamedIconChooserDialog, glade_named_icon_chooser_dialog, GLADE, NAMED_ICON_CHOOSER_DIALOG, GtkDialog) + +struct _GladeNamedIconChooserDialogClass +{ + GtkDialogClass parent_class; + + void (* icon_activated) (GladeNamedIconChooserDialog *dialog); + + void (* selection_changed) (GladeNamedIconChooserDialog *dialog); + + gpointer padding[4]; +}; + +GtkWidget *glade_named_icon_chooser_dialog_new (const gchar *title, + GtkWindow *parent, + const gchar *first_button_text, + ...) G_GNUC_NULL_TERMINATED; + +gchar *glade_named_icon_chooser_dialog_get_icon_name (GladeNamedIconChooserDialog *chooser); + +void glade_named_icon_chooser_dialog_set_icon_name (GladeNamedIconChooserDialog *chooser, + const gchar *icon_name); + +gboolean glade_named_icon_chooser_dialog_set_context (GladeNamedIconChooserDialog *chooser, + const gchar *context); + +gchar *glade_named_icon_chooser_dialog_get_context (GladeNamedIconChooserDialog *chooser); + +G_END_DECLS + +#endif /* __GLADE_NAMED_ICON_CHOOSER_DIALOG_H__ */ diff --git a/gladeui/glade-object-stub.c b/gladeui/glade-object-stub.c new file mode 100644 index 0000000..c779539 --- /dev/null +++ b/gladeui/glade-object-stub.c @@ -0,0 +1,211 @@ +/* + * glade-object-stub.c + * + * Copyright (C) 2011 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include + +#include +#include "glade-object-stub.h" +#include "glade-project.h" + +struct _GladeObjectStub +{ + GtkInfoBar parent_instance; + + GtkLabel *label; + + gchar *type; + GladeXmlNode *node; +}; + +enum +{ + PROP_0, + + PROP_OBJECT_TYPE, + PROP_XML_NODE +}; + + +G_DEFINE_TYPE (GladeObjectStub, glade_object_stub, GTK_TYPE_INFO_BAR); + +#define RESPONSE_DELETE 1 +#define RESPONSE_DELETE_ALL 2 + +static void +on_infobar_response (GladeObjectStub *stub, gint response_id) +{ + GladeWidget *gwidget = glade_widget_get_from_gobject (stub); + + if (response_id == RESPONSE_DELETE) + { + GList widgets = {0, }; + widgets.data = gwidget; + glade_command_delete (&widgets); + } + else if (response_id == RESPONSE_DELETE_ALL) + { + GladeProject *project = glade_widget_get_project (gwidget); + GList *stubs = NULL; + const GList *l; + + for (l = glade_project_get_objects (project); l; l = g_list_next (l)) + { + if (GLADE_IS_OBJECT_STUB (l->data)) + { + GladeWidget *gobj = glade_widget_get_from_gobject (l->data); + stubs = g_list_prepend (stubs, gobj); + } + } + + glade_command_delete (stubs); + g_list_free (stubs); + } +} + +static void +glade_object_stub_init (GladeObjectStub *self) +{ + GtkWidget *label = gtk_label_new (NULL); + + self->type = NULL; + self->node = NULL; + + self->label = GTK_LABEL (label); + gtk_label_set_line_wrap (self->label, TRUE); + + gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (self))), label); + + gtk_info_bar_add_button (GTK_INFO_BAR (self), + _("Delete"), RESPONSE_DELETE); + gtk_info_bar_add_button (GTK_INFO_BAR (self), + _("Delete All"), RESPONSE_DELETE_ALL); + + g_signal_connect (self, "response", G_CALLBACK (on_infobar_response), NULL); +} + +static void +glade_object_stub_finalize (GObject *object) +{ + GladeObjectStub *self = (GladeObjectStub *) object; + + g_clear_pointer (&self->type, g_free); + g_clear_pointer (&self->node, glade_xml_node_delete); + + G_OBJECT_CLASS (glade_object_stub_parent_class)->finalize (object); +} + +static void +glade_object_stub_refresh_text (GladeObjectStub *self) +{ + gchar *markup; + GType type; + + if (self->type == NULL) return; + + type = g_type_from_name (self->type); + + if ((type != G_TYPE_INVALID && (!G_TYPE_IS_INSTANTIATABLE (type) || G_TYPE_IS_ABSTRACT (type)))) + markup = g_markup_printf_escaped ("FIXME: Unable to create uninstantiable object with type %s", self->type); + else + markup = g_markup_printf_escaped ("FIXME: Unable to create object with type %s", self->type); + + gtk_label_set_markup (self->label, markup); + gtk_info_bar_set_message_type (GTK_INFO_BAR (self), GTK_MESSAGE_WARNING); + g_free (markup); +} + +static void +glade_object_stub_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeObjectStub *self = (GladeObjectStub *) object; + + g_return_if_fail (GLADE_IS_OBJECT_STUB (object)); + + switch (prop_id) + { + case PROP_OBJECT_TYPE: + g_free (self->type); + self->type = g_value_dup_string (value); + glade_object_stub_refresh_text (self); + break; + case PROP_XML_NODE: + if (self->node) glade_xml_node_delete (self->node); + self->node = g_value_dup_boxed (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_object_stub_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeObjectStub *self = (GladeObjectStub *) object; + + g_return_if_fail (GLADE_IS_OBJECT_STUB (object)); + + switch (prop_id) + { + case PROP_OBJECT_TYPE: + g_value_set_string (value, self->type); + break; + case PROP_XML_NODE: + g_value_set_boxed (value, self->node); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_object_stub_class_init (GladeObjectStubClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = glade_object_stub_finalize; + object_class->set_property = glade_object_stub_set_property; + object_class->get_property = glade_object_stub_get_property; + + g_object_class_install_property (object_class, + PROP_OBJECT_TYPE, + g_param_spec_string ("object-type", + "Object Type", + "The object type this stub replaces", + NULL, + G_PARAM_READWRITE)); + g_object_class_install_property (object_class, + PROP_XML_NODE, + g_param_spec_boxed ("xml-node", + "XML node", + "The XML representation of the original object this is replacing", + glade_xml_node_get_type (), + G_PARAM_READWRITE)); +} diff --git a/gladeui/glade-object-stub.h b/gladeui/glade-object-stub.h new file mode 100644 index 0000000..0bdf53d --- /dev/null +++ b/gladeui/glade-object-stub.h @@ -0,0 +1,36 @@ +/* + * glade-object-stub.h + * + * Copyright (C) 2011 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _GLADE_OBJECT_STUB_H_ +#define _GLADE_OBJECT_STUB_H_ + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_OBJECT_STUB glade_object_stub_get_type () +G_DECLARE_FINAL_TYPE (GladeObjectStub, glade_object_stub, GLADE, OBJECT_STUB, GtkInfoBar) + +G_END_DECLS + +#endif /* _GLADE_OBJECT_STUB_H_ */ diff --git a/gladeui/glade-palette.c b/gladeui/glade-palette.c new file mode 100644 index 0000000..bc25246 --- /dev/null +++ b/gladeui/glade-palette.c @@ -0,0 +1,900 @@ +/* + * glade-palette.c + * + * Copyright (C) 2006 The GNOME Foundation. + * Copyright (C) 2001-2005 Ximian, Inc. + * + * Authors: + * Chema Celorio + * Joaquin Cuenca Abela + * Vincent Geddes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifdef HAVE_CONFIG_H +#include +#endif + +/** + * SECTION:glade-palette + * @Short_Description: A widget to select a #GladeWidgetAdaptor for addition. + * + * #GladePalette is responsible for displaying the list of available + * #GladeWidgetAdaptor types and publishing the currently selected class + * to the Glade core. + */ + +#include "glade.h" +#include "gladeui-enum-types.h" +#include "glade-app.h" +#include "glade-palette.h" +#include "glade-catalog.h" +#include "glade-project.h" +#include "glade-widget.h" +#include "glade-widget-adaptor.h" +#include "glade-popup.h" +#include "glade-design-private.h" +#include "glade-dnd.h" + +#include +#include + +struct _GladePalettePrivate +{ + const GList *catalogs; /* List of widget catalogs */ + + GladeProject *project; + + GtkWidget *selector_hbox; + GtkWidget *selector_button; + + GtkWidget *toolpalette; + + GladeItemAppearance item_appearance; + gboolean use_small_item_icons; + + GladeWidgetAdaptor *local_selection; + GHashTable *button_table; +}; + +enum +{ + REFRESH, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_ITEM_APPEARANCE, + PROP_USE_SMALL_ITEM_ICONS, + PROP_SHOW_SELECTOR_BUTTON, + PROP_PROJECT, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES]; +static guint glade_palette_signals[LAST_SIGNAL] = { 0 }; + +static void glade_palette_append_item_group (GladePalette *palette, + GladeWidgetGroup *group); +static void palette_item_toggled_cb (GtkToggleToolButton *button, + GladePalette *palette); + +G_DEFINE_TYPE_WITH_PRIVATE (GladePalette, glade_palette, GTK_TYPE_BOX) + + +/******************************************************* + * Project Signals * + *******************************************************/ +static void +palette_item_refresh_cb (GladePalette *palette, + GtkWidget *item) +{ + GladeProject *project; + GladeSupportMask support; + GladeWidgetAdaptor *adaptor; + gchar *warning, *text; + + adaptor = g_object_get_data (G_OBJECT (item), "glade-widget-adaptor"); + g_assert (adaptor); + + if ((project = palette->priv->project) && + (warning = glade_project_verify_widget_adaptor (project, adaptor, + &support)) != NULL) + { + /* set sensitivity */ + gtk_widget_set_sensitive (GTK_WIDGET (item), + !(support & GLADE_SUPPORT_MISMATCH)); + + gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (item), + glade_widget_adaptor_get_icon_name (adaptor)); + + /* prepend widget title */ + text = g_strdup_printf ("%s: %s", glade_widget_adaptor_get_title (adaptor), warning); + gtk_widget_set_tooltip_text (item, text); + g_free (text); + g_free (warning); + } + else + { + gtk_widget_set_tooltip_text (GTK_WIDGET (item), + glade_widget_adaptor_get_title (adaptor)); + gtk_widget_set_sensitive (GTK_WIDGET (item), TRUE); + gtk_tool_button_set_icon_name (GTK_TOOL_BUTTON (item), + glade_widget_adaptor_get_icon_name (adaptor)); + } +} + +static void +glade_palette_refresh (GladePalette *palette) +{ + g_return_if_fail (GLADE_IS_PALETTE (palette)); + + g_signal_emit (G_OBJECT (palette), glade_palette_signals[REFRESH], 0); +} + +static void +project_add_item_changed_cb (GladeProject *project, + GParamSpec *pspec, + GladePalette *palette) +{ + GtkToggleToolButton *selection = NULL; + GladePalettePrivate *priv = palette->priv; + + if (priv->local_selection) + { + selection = g_hash_table_lookup (priv->button_table, + glade_widget_adaptor_get_name (priv->local_selection)); + + g_signal_handlers_block_by_func (selection, palette_item_toggled_cb, palette); + gtk_toggle_tool_button_set_active (selection, FALSE); + g_signal_handlers_unblock_by_func (selection, palette_item_toggled_cb, palette); + + glade_project_set_pointer_mode (priv->project, GLADE_POINTER_SELECT); + } + + priv->local_selection = glade_project_get_add_item (priv->project); + + if (priv->local_selection) + { + selection = g_hash_table_lookup (priv->button_table, + glade_widget_adaptor_get_name (priv->local_selection)); + + g_signal_handlers_block_by_func (selection, palette_item_toggled_cb, palette); + gtk_toggle_tool_button_set_active (selection, TRUE); + g_signal_handlers_unblock_by_func (selection, palette_item_toggled_cb, palette); + + glade_project_set_pointer_mode (priv->project, GLADE_POINTER_ADD_WIDGET); + } +} + +/******************************************************* + * Local Signals * + *******************************************************/ +static void +selector_button_toggled_cb (GtkToggleButton *button, + GladePalette *palette) +{ + GladePalettePrivate *priv = palette->priv; + + if (!priv->project) + return; + + if (gtk_toggle_button_get_active (button)) + { + g_signal_handlers_block_by_func (priv->project, project_add_item_changed_cb, palette); + glade_project_set_add_item (priv->project, NULL); + g_signal_handlers_unblock_by_func (priv->project, project_add_item_changed_cb, palette); + } + else if (glade_project_get_add_item (priv->project) == NULL) + gtk_toggle_button_set_active (button, TRUE); +} + +static void +palette_item_toggled_cb (GtkToggleToolButton *button, GladePalette *palette) +{ + GladePalettePrivate *priv = palette->priv; + GladeWidgetAdaptor *adaptor; + GtkToggleToolButton *selection = NULL; + + if (!priv->project) + return; + + adaptor = g_object_get_data (G_OBJECT (button), "glade-widget-adaptor"); + if (!adaptor) + return; + + /* Start by unselecting the currently selected item if any */ + if (priv->local_selection) + { + selection = g_hash_table_lookup (priv->button_table, + glade_widget_adaptor_get_name (priv->local_selection)); + + g_signal_handlers_block_by_func (selection, palette_item_toggled_cb, palette); + gtk_toggle_tool_button_set_active (selection, FALSE); + g_signal_handlers_unblock_by_func (selection, palette_item_toggled_cb, palette); + + priv->local_selection = NULL; + + g_signal_handlers_block_by_func (priv->project, project_add_item_changed_cb, palette); + glade_project_set_add_item (priv->project, NULL); + g_signal_handlers_unblock_by_func (priv->project, project_add_item_changed_cb, palette); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->selector_button), TRUE); + + glade_project_set_pointer_mode (priv->project, GLADE_POINTER_SELECT); + } + + if (!gtk_toggle_tool_button_get_active (button)) + return; + + /* Auto-create toplevel types */ + if (GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (adaptor)) + { + glade_command_create (adaptor, NULL, NULL, priv->project); + + g_signal_handlers_block_by_func (button, palette_item_toggled_cb, palette); + gtk_toggle_tool_button_set_active (button, FALSE); + g_signal_handlers_unblock_by_func (button, palette_item_toggled_cb, palette); + } + else + { + g_signal_handlers_block_by_func (priv->project, project_add_item_changed_cb, palette); + glade_project_set_add_item (priv->project, adaptor); + g_signal_handlers_unblock_by_func (priv->project, project_add_item_changed_cb, palette); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->selector_button), FALSE); + + priv->local_selection = adaptor; + + glade_project_set_pointer_mode (priv->project, GLADE_POINTER_ADD_WIDGET); + } +} + +static void +glade_palette_drag_begin (GtkWidget *widget, + GdkDragContext *context, + GladeWidgetAdaptor *adaptor) +{ + _glade_dnd_set_icon_widget (context, + glade_widget_adaptor_get_icon_name (adaptor), + glade_widget_adaptor_get_display_name (adaptor)); +} + +static void +glade_palette_drag_data_get (GtkWidget *widget, + GdkDragContext *context, + GtkSelectionData *data, + guint info, + guint time, + GladeWidgetAdaptor *adaptor) +{ + _glade_dnd_set_data (data, G_OBJECT (adaptor)); +} + +static gint +palette_item_button_press_cb (GtkWidget *button, + GdkEventButton *event, + GtkToolItem *item) +{ + GladePalette *palette = g_object_get_data (G_OBJECT (item), "glade-palette"); + GladeWidgetAdaptor *adaptor = g_object_get_data (G_OBJECT (item), "glade-widget-adaptor"); + + if (glade_popup_is_popup_event (event)) + { + glade_popup_palette_pop (palette, adaptor, event); + return TRUE; + } + + return FALSE; +} + +/******************************************************* + * Building Widgets/Populating catalog * + *******************************************************/ +static GtkWidget * +glade_palette_new_item (GladePalette *palette, GladeWidgetAdaptor *adaptor) +{ + GtkWidget *item, *button, *label, *box; + + item = (GtkWidget *) gtk_toggle_tool_button_new (); + g_object_set_data (G_OBJECT (item), "glade-widget-adaptor", adaptor); + g_object_set_data (G_OBJECT (item), "glade-palette", palette); + + button = gtk_bin_get_child (GTK_BIN (item)); + g_assert (GTK_IS_BUTTON (button)); + + /* Add a box to avoid the ellipsize on the items */ + box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + label = gtk_label_new (glade_widget_adaptor_get_title (adaptor)); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_widget_show (label); + gtk_widget_show (box); + gtk_container_add (GTK_CONTAINER (box), label); + gtk_tool_button_set_label_widget (GTK_TOOL_BUTTON (item), box); + palette_item_refresh_cb (palette, item); + + /* Update selection when the item is pushed */ + g_signal_connect (G_OBJECT (item), "toggled", + G_CALLBACK (palette_item_toggled_cb), palette); + + /* Update palette item when active project state changes */ + g_signal_connect (G_OBJECT (palette), "refresh", + G_CALLBACK (palette_item_refresh_cb), item); + + /* Fire Glade palette popup menus */ + g_signal_connect (G_OBJECT (button), "button-press-event", + G_CALLBACK (palette_item_button_press_cb), item); + g_signal_connect_object (button, "drag-begin", + G_CALLBACK (glade_palette_drag_begin), adaptor, 0); + g_signal_connect_object (button, "drag-data-get", + G_CALLBACK (glade_palette_drag_data_get), adaptor, 0); + + gtk_drag_source_set (button, GDK_BUTTON1_MASK, _glade_dnd_get_target (), 1, GDK_ACTION_MOVE | GDK_ACTION_COPY); + + gtk_widget_show (item); + + g_hash_table_insert (palette->priv->button_table, + (gchar *)glade_widget_adaptor_get_name (adaptor), + item); + + return item; +} + +static GtkWidget * +glade_palette_new_item_group (GladePalette *palette, GladeWidgetGroup *group) +{ + GtkWidget *item_group, *item, *label; + GList *l; + + /* Give the item group a left aligned label */ + label = gtk_label_new (glade_widget_group_get_title (group)); + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_widget_show (label); + + item_group = gtk_tool_item_group_new (""); + gtk_tool_item_group_set_label_widget (GTK_TOOL_ITEM_GROUP (item_group), + label); + + /* Tell the item group to ellipsize our custom label for us */ + gtk_tool_item_group_set_ellipsize (GTK_TOOL_ITEM_GROUP (item_group), + PANGO_ELLIPSIZE_END); + + gtk_widget_set_tooltip_text (item_group, + glade_widget_group_get_title (group)); + + /* Go through all the widget classes in this catalog. */ + for (l = (GList *) glade_widget_group_get_adaptors (group); l; l = l->next) + { + GladeWidgetAdaptor *adaptor = GLADE_WIDGET_ADAPTOR (l->data); + + /* Create/append new item */ + item = glade_palette_new_item (palette, adaptor); + gtk_tool_item_group_insert (GTK_TOOL_ITEM_GROUP (item_group), + GTK_TOOL_ITEM (item), -1); + } + + /* set default expanded state */ + gtk_tool_item_group_set_collapsed (GTK_TOOL_ITEM_GROUP (item_group), + glade_widget_group_get_expanded (group) == FALSE); + + gtk_widget_show (item_group); + + return item_group; +} + +static void +glade_palette_append_item_group (GladePalette *palette, GladeWidgetGroup *group) +{ + GladePalettePrivate *priv = palette->priv; + GtkWidget *item_group; + + if ((item_group = glade_palette_new_item_group (palette, group)) != NULL) + gtk_container_add (GTK_CONTAINER (priv->toolpalette), item_group); +} + +static void +glade_palette_populate (GladePalette *palette) +{ + GList *l; + + g_return_if_fail (GLADE_IS_PALETTE (palette)); + + for (l = (GList *) glade_app_get_catalogs (); l; l = l->next) + { + GList *groups = glade_catalog_get_widget_groups (GLADE_CATALOG (l->data)); + + for (; groups; groups = groups->next) + { + GladeWidgetGroup *group = GLADE_WIDGET_GROUP (groups->data); + + if (glade_widget_group_get_adaptors (group)) + glade_palette_append_item_group (palette, group); + } + } +} + +static GtkWidget * +glade_palette_create_selector_button (GladePalette *palette) +{ + GtkWidget *selector; + GtkWidget *image; + gchar *path; + + /* create selector button */ + selector = gtk_toggle_button_new (); + + gtk_container_set_border_width (GTK_CONTAINER (selector), 0); + + path = g_build_filename (glade_app_get_pixmaps_dir (), "selector.png", NULL); + image = gtk_image_new_from_file (path); + gtk_widget_show (image); + + gtk_container_add (GTK_CONTAINER (selector), image); + gtk_button_set_relief (GTK_BUTTON (selector), GTK_RELIEF_NONE); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (selector), TRUE); + + g_signal_connect (G_OBJECT (selector), "toggled", + G_CALLBACK (selector_button_toggled_cb), palette); + + g_free (path); + + return selector; +} + +/******************************************************* + * Class & methods * + *******************************************************/ + +/* override GtkWidget::show_all since we have internal widgets we wish to keep + * hidden unless we decide otherwise, like the hidden selector button. + */ +static void +glade_palette_show_all (GtkWidget *widget) +{ + gtk_widget_show (widget); +} + +static void +glade_palette_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladePalette *palette = GLADE_PALETTE (object); + + switch (prop_id) + { + case PROP_PROJECT: + glade_palette_set_project (palette, (GladeProject *)g_value_get_object (value)); + break; + case PROP_USE_SMALL_ITEM_ICONS: + glade_palette_set_use_small_item_icons (palette, + g_value_get_boolean (value)); + break; + case PROP_ITEM_APPEARANCE: + glade_palette_set_item_appearance (palette, g_value_get_enum (value)); + break; + case PROP_SHOW_SELECTOR_BUTTON: + glade_palette_set_show_selector_button (palette, + g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_palette_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladePalette *palette = GLADE_PALETTE (object); + GladePalettePrivate *priv = palette->priv; + + switch (prop_id) + { + case PROP_PROJECT: + g_value_set_object (value, priv->project); + break; + case PROP_USE_SMALL_ITEM_ICONS: + g_value_set_boolean (value, priv->use_small_item_icons); + break; + case PROP_SHOW_SELECTOR_BUTTON: + g_value_set_boolean (value, + gtk_widget_get_visible (priv->selector_button)); + break; + case PROP_ITEM_APPEARANCE: + g_value_set_enum (value, priv->item_appearance); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_palette_dispose (GObject *object) +{ + GladePalettePrivate *priv; + + priv = GLADE_PALETTE (object)->priv; + + priv->catalogs = NULL; + + glade_palette_set_project (GLADE_PALETTE (object), NULL); + + G_OBJECT_CLASS (glade_palette_parent_class)->dispose (object); +} + +static void +glade_palette_finalize (GObject *object) +{ + GladePalettePrivate *priv; + + priv = GLADE_PALETTE (object)->priv; + + g_hash_table_destroy (priv->button_table); + + G_OBJECT_CLASS (glade_palette_parent_class)->finalize (object); +} + +static void +glade_palette_class_init (GladePaletteClass *klass) +{ + GObjectClass *object_class; + GtkWidgetClass *widget_class; + + object_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + object_class->get_property = glade_palette_get_property; + object_class->set_property = glade_palette_set_property; + object_class->dispose = glade_palette_dispose; + object_class->finalize = glade_palette_finalize; + + widget_class->show_all = glade_palette_show_all; + + glade_palette_signals[REFRESH] = + g_signal_new ("refresh", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladePaletteClass, refresh), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + properties[PROP_PROJECT] = + g_param_spec_object ("project", + "Project", + "This palette's current project", + GLADE_TYPE_PROJECT, + G_PARAM_READWRITE); + + properties[PROP_ITEM_APPEARANCE] = + g_param_spec_enum ("item-appearance", + "Item Appearance", + "The appearance of the palette items", + GLADE_TYPE_ITEM_APPEARANCE, + GLADE_ITEM_ICON_ONLY, + G_PARAM_READWRITE); + + properties[PROP_USE_SMALL_ITEM_ICONS] = + g_param_spec_boolean ("use-small-item-icons", + "Use Small Item Icons", + "Whether to use small icons to represent items", + FALSE, + G_PARAM_READWRITE); + + properties[PROP_SHOW_SELECTOR_BUTTON] = + g_param_spec_boolean ("show-selector-button", + "Show Selector Button", + "Whether to show the internal selector button", + TRUE, + G_PARAM_READWRITE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, properties); +} + +static void +glade_palette_init (GladePalette *palette) +{ + GladePalettePrivate *priv; + GtkWidget *sw; + + gtk_orientable_set_orientation (GTK_ORIENTABLE (palette), + GTK_ORIENTATION_VERTICAL); + + priv = palette->priv = glade_palette_get_instance_private (palette); + + priv->button_table = g_hash_table_new (g_str_hash, g_str_equal); + + priv->item_appearance = GLADE_ITEM_ICON_ONLY; + priv->use_small_item_icons = FALSE; + + /* create selector button */ + priv->selector_button = glade_palette_create_selector_button (palette); + priv->selector_hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 0); + gtk_box_pack_start (GTK_BOX (priv->selector_hbox), priv->selector_button, + FALSE, FALSE, 0); + gtk_box_pack_start (GTK_BOX (palette), priv->selector_hbox, FALSE, FALSE, 0); + gtk_widget_show (priv->selector_button); + gtk_widget_show (priv->selector_hbox); + + gtk_widget_set_tooltip_text (priv->selector_button, _("Widget selector")); + + /* The GtkToolPalette */ + priv->toolpalette = gtk_tool_palette_new (); + gtk_tool_palette_set_style (GTK_TOOL_PALETTE (priv->toolpalette), + GTK_TOOLBAR_ICONS); + gtk_tool_palette_set_icon_size (GTK_TOOL_PALETTE (priv->toolpalette), + GTK_ICON_SIZE_LARGE_TOOLBAR); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), + GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), + GTK_SHADOW_NONE); + + gtk_container_add (GTK_CONTAINER (sw), priv->toolpalette); + gtk_box_pack_start (GTK_BOX (palette), sw, TRUE, TRUE, 0); + + gtk_widget_show (sw); + gtk_widget_show (priv->toolpalette); + gtk_widget_set_no_show_all (GTK_WIDGET (palette), TRUE); + + glade_palette_populate (palette); +} + + +/******************************************************* + * API * + *******************************************************/ + +/** + * glade_palette_new: + * + * Creates a new #GladePalette widget + * + * Returns: a new #GladePalette + */ +GtkWidget * +glade_palette_new (void) +{ + GladePalette *palette; + + palette = g_object_new (GLADE_TYPE_PALETTE, + "spacing", 2, + "item-appearance", GLADE_ITEM_ICON_ONLY, + NULL); + + return GTK_WIDGET (palette); +} + +/** + * glade_palette_get_project: + * @palette: a #GladePalette + * + * Returns: (transfer none): a #GladeProject + */ +GladeProject * +glade_palette_get_project (GladePalette *palette) +{ + g_return_val_if_fail (GLADE_IS_PALETTE (palette), NULL); + + return palette->priv->project; +} + +void +glade_palette_set_project (GladePalette *palette, GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PALETTE (palette)); + + if (palette->priv->project != project) + { + if (palette->priv->project) + { + g_signal_handlers_disconnect_by_func (G_OBJECT (palette->priv->project), + G_CALLBACK (glade_palette_refresh), + palette); + + g_signal_handlers_disconnect_by_func (G_OBJECT (palette->priv->project), + G_CALLBACK (project_add_item_changed_cb), + palette); + + g_object_unref (palette->priv->project); + } + + palette->priv->project = project; + + if (palette->priv->project) + { + g_signal_connect_swapped (G_OBJECT (palette->priv->project), "targets-changed", + G_CALLBACK (glade_palette_refresh), palette); + g_signal_connect_swapped (G_OBJECT (palette->priv->project), "parse-finished", + G_CALLBACK (glade_palette_refresh), palette); + + g_signal_connect (G_OBJECT (palette->priv->project), "notify::add-item", + G_CALLBACK (project_add_item_changed_cb), palette); + + g_object_ref (palette->priv->project); + + project_add_item_changed_cb (project, NULL, palette); + } + + glade_palette_refresh (palette); + + g_object_notify_by_pspec (G_OBJECT (palette), properties[PROP_PROJECT]); + } +} + +/** + * glade_palette_set_item_appearance: + * @palette: a #GladePalette + * @item_appearance: the item appearance + * + * Sets the appearance of the palette items. + */ +void +glade_palette_set_item_appearance (GladePalette *palette, + GladeItemAppearance item_appearance) +{ + GladePalettePrivate *priv; + + g_return_if_fail (GLADE_IS_PALETTE (palette)); + + priv = palette->priv; + + if (priv->item_appearance != item_appearance) + { + GtkToolbarStyle style; + priv->item_appearance = item_appearance; + + switch (item_appearance) + { + case GLADE_ITEM_ICON_AND_LABEL: + style = GTK_TOOLBAR_BOTH_HORIZ; + break; + case GLADE_ITEM_ICON_ONLY: + style = GTK_TOOLBAR_ICONS; + break; + case GLADE_ITEM_LABEL_ONLY: + style = GTK_TOOLBAR_TEXT; + break; + default: + g_assert_not_reached (); + break; + } + + gtk_tool_palette_set_style (GTK_TOOL_PALETTE (priv->toolpalette), style); + + g_object_notify_by_pspec (G_OBJECT (palette), properties[PROP_ITEM_APPEARANCE]); + } +} + +/** + * glade_palette_set_use_small_item_icons: + * @palette: a #GladePalette + * @use_small_item_icons: Whether to use small item icons + * + * Sets whether to use small item icons. + */ +void +glade_palette_set_use_small_item_icons (GladePalette *palette, + gboolean use_small_item_icons) +{ + GladePalettePrivate *priv; + g_return_if_fail (GLADE_IS_PALETTE (palette)); + priv = palette->priv; + + if (priv->use_small_item_icons != use_small_item_icons) + { + priv->use_small_item_icons = use_small_item_icons; + + gtk_tool_palette_set_icon_size (GTK_TOOL_PALETTE (priv->toolpalette), + (use_small_item_icons) ? + GTK_ICON_SIZE_SMALL_TOOLBAR : + GTK_ICON_SIZE_LARGE_TOOLBAR); + + g_object_notify_by_pspec (G_OBJECT (palette), properties[PROP_USE_SMALL_ITEM_ICONS]); + } +} + +/** + * glade_palette_set_show_selector_button: + * @palette: a #GladePalette + * @show_selector_button: whether to show selector button + * + * Sets whether to show the internal widget selector button + */ +void +glade_palette_set_show_selector_button (GladePalette *palette, + gboolean show_selector_button) +{ + GladePalettePrivate *priv; + g_return_if_fail (GLADE_IS_PALETTE (palette)); + priv = palette->priv; + + if (gtk_widget_get_visible (priv->selector_hbox) != show_selector_button) + { + if (show_selector_button) + gtk_widget_show (priv->selector_hbox); + else + gtk_widget_hide (priv->selector_hbox); + + g_object_notify_by_pspec (G_OBJECT (palette), properties[PROP_SHOW_SELECTOR_BUTTON]); + + } + +} + +/** + * glade_palette_get_item_appearance: + * @palette: a #GladePalette + * + * Returns: The appearance of the palette items + */ +GladeItemAppearance +glade_palette_get_item_appearance (GladePalette *palette) +{; + g_return_val_if_fail (GLADE_IS_PALETTE (palette), GLADE_ITEM_ICON_ONLY); + + return palette->priv->item_appearance; +} + +/** + * glade_palette_get_use_small_item_icons: + * @palette: a #GladePalette + * + * Returns: Whether small item icons are used + */ +gboolean +glade_palette_get_use_small_item_icons (GladePalette *palette) +{ + g_return_val_if_fail (GLADE_IS_PALETTE (palette), FALSE); + + return palette->priv->use_small_item_icons; +} + +/** + * glade_palette_get_show_selector_button: + * @palette: a #GladePalette + * + * Returns: Whether the selector button is visible + */ +gboolean +glade_palette_get_show_selector_button (GladePalette *palette) +{ + g_return_val_if_fail (GLADE_IS_PALETTE (palette), FALSE); + + return gtk_widget_get_visible (palette->priv->selector_hbox); +} + +/** + * glade_palette_get_tool_palette: + * @palette: a #GladePalette + * + * Returns: (transfer none): the GtkToolPalette associated to this palette. + */ +GtkToolPalette * +glade_palette_get_tool_palette (GladePalette *palette) +{ + g_return_val_if_fail (GLADE_IS_PALETTE (palette), FALSE); + + return GTK_TOOL_PALETTE (palette->priv->toolpalette); +} diff --git a/gladeui/glade-palette.h b/gladeui/glade-palette.h new file mode 100644 index 0000000..37d38c6 --- /dev/null +++ b/gladeui/glade-palette.h @@ -0,0 +1,103 @@ +/* + * glade-palette.h + * + * Copyright (C) 2006 The GNOME Foundation. + * Copyright (C) 2001-2005 Ximian, Inc. + * + * Authors: + * Chema Celorio + * Joaquin Cuenca Abela + * Vincent Geddes + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GLADE_PALETTE_H__ +#define __GLADE_PALETTE_H__ + +#include + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PALETTE (glade_palette_get_type ()) +#define GLADE_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PALETTE, GladePalette)) +#define GLADE_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PALETTE, GladePaletteClass)) +#define GLADE_IS_PALETTE(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PALETTE)) +#define GLADE_IS_PALETTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PALETTE)) +#define GLADE_PALETTE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PALETTE, GladePaletteClass)) + +typedef struct _GladePalette GladePalette; +typedef struct _GladePalettePrivate GladePalettePrivate; +typedef struct _GladePaletteClass GladePaletteClass; + +struct _GladePalette +{ + GtkBox parent_instance; + + GladePalettePrivate *priv; +}; + +struct _GladePaletteClass +{ + GtkBoxClass parent_class; + + void (* refresh) (GladePalette *palette); + + void (* glade_reserved1) (void); + void (* glade_reserved2) (void); + void (* glade_reserved3) (void); + void (* glade_reserved4) (void); +}; + +typedef enum +{ + GLADE_ITEM_ICON_AND_LABEL, + GLADE_ITEM_ICON_ONLY, + GLADE_ITEM_LABEL_ONLY +} GladeItemAppearance; + + +GType glade_palette_get_type (void) G_GNUC_CONST; + +GtkWidget *glade_palette_new (void); + +GladeProject *glade_palette_get_project (GladePalette *palette); +void glade_palette_set_project (GladePalette *palette, + GladeProject *project); + +GladeItemAppearance glade_palette_get_item_appearance (GladePalette *palette); + +void glade_palette_set_item_appearance (GladePalette *palette, + GladeItemAppearance item_appearance); + +gboolean glade_palette_get_use_small_item_icons (GladePalette *palette); + +void glade_palette_set_use_small_item_icons (GladePalette *palette, + gboolean use_small_item_icons); + +void glade_palette_set_show_selector_button (GladePalette *palette, + gboolean show_selector_button); + +gboolean glade_palette_get_show_selector_button (GladePalette *palette); + +GtkToolPalette *glade_palette_get_tool_palette (GladePalette *palette); + +G_END_DECLS + +#endif /* __GLADE_PALETTE_H__ */ + diff --git a/gladeui/glade-path.h b/gladeui/glade-path.h new file mode 100644 index 0000000..6418587 --- /dev/null +++ b/gladeui/glade-path.h @@ -0,0 +1,408 @@ +#ifndef __glade_path_H__ +#define __glade_path_H__ + +#define glade_path_WIDTH 408.781250 +#define glade_path_HEIGHT 398.937500 + +cairo_path_data_t glade_path_data[] = { + {.header.type = 0, .header.length = 2}, + {.point.x = 90.343750, .point.y = 0.000000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 89.687500, .point.y = 2.250000}, + {.point.x = 89.421875, .point.y = 10.992188}, + {.point.x = 89.812500, .point.y = 19.406250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 90.441406, .point.y = 32.828125}, + {.point.x = 91.304688, .point.y = 35.968750}, + {.point.x = 96.500000, .point.y = 45.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 100.148438, .point.y = 51.394531}, + {.point.x = 105.625000, .point.y = 57.781250}, + {.point.x = 110.750000, .point.y = 61.656250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 120.437500, .point.y = 68.976562}, + {.point.x = 144.214844, .point.y = 79.347656}, + {.point.x = 171.375000, .point.y = 88.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 196.046875, .point.y = 95.921875}, + {.point.x = 204.484375, .point.y = 99.640625}, + {.point.x = 211.781250, .point.y = 106.000000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 215.050781, .point.y = 108.847656}, + {.point.x = 217.988281, .point.y = 110.636719}, + {.point.x = 218.312500, .point.y = 109.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 218.640625, .point.y = 109.242188}, + {.point.x = 218.062500, .point.y = 102.550781}, + {.point.x = 217.000000, .point.y = 95.093750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 214.566406, .point.y = 78.011719}, + {.point.x = 207.527344, .point.y = 64.671875}, + {.point.x = 194.875000, .point.y = 53.187500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 182.554688, .point.y = 42.000000}, + {.point.x = 172.675781, .point.y = 36.773438}, + {.point.x = 149.531250, .point.y = 29.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 113.113281, .point.y = 16.847656}, + {.point.x = 106.210938, .point.y = 13.308594}, + {.point.x = 96.906250, .point.y = 2.281250}, + {.header.type = 3, .header.length = 1}, + {.header.type = 0, .header.length = 2}, + {.point.x = 238.031250, .point.y = 58.656250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 237.492188, .point.y = 58.968750}, + {.point.x = 239.261719, .point.y = 61.609375}, + {.point.x = 242.156250, .point.y = 64.750000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 245.250000, .point.y = 68.101562}, + {.point.x = 250.398438, .point.y = 75.605469}, + {.point.x = 253.593750, .point.y = 81.468750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 258.394531, .point.y = 90.273438}, + {.point.x = 259.523438, .point.y = 94.562500}, + {.point.x = 260.156250, .point.y = 105.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 261.109375, .point.y = 123.046875}, + {.point.x = 258.335938, .point.y = 134.652344}, + {.point.x = 249.781250, .point.y = 149.468750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 244.441406, .point.y = 158.726562}, + {.point.x = 242.363281, .point.y = 160.906250}, + {.point.x = 238.968750, .point.y = 160.906250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 235.425781, .point.y = 160.906250}, + {.point.x = 234.160156, .point.y = 159.308594}, + {.point.x = 230.718750, .point.y = 150.531250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 221.054688, .point.y = 125.871094}, + {.point.x = 203.195312, .point.y = 113.500000}, + {.point.x = 167.750000, .point.y = 107.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 145.222656, .point.y = 102.921875}, + {.point.x = 134.351562, .point.y = 99.855469}, + {.point.x = 123.000000, .point.y = 94.375000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 113.082031, .point.y = 89.589844}, + {.point.x = 98.183594, .point.y = 76.960938}, + {.point.x = 94.687500, .point.y = 70.375000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 91.265625, .point.y = 63.921875}, + {.point.x = 89.593750, .point.y = 68.769531}, + {.point.x = 89.562500, .point.y = 85.343750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 89.519531, .point.y = 110.410156}, + {.point.x = 99.582031, .point.y = 135.527344}, + {.point.x = 116.437500, .point.y = 152.437500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 123.062500, .point.y = 159.062500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 114.343750, .point.y = 158.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 109.535156, .point.y = 157.464844}, + {.point.x = 102.035156, .point.y = 155.664062}, + {.point.x = 97.656250, .point.y = 154.000000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 87.226562, .point.y = 150.039062}, + {.point.x = 72.132812, .point.y = 137.804688}, + {.point.x = 66.750000, .point.y = 128.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 64.394531, .point.y = 125.054688}, + {.point.x = 61.109375, .point.y = 113.023438}, + {.point.x = 61.062500, .point.y = 114.500000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 60.906250, .point.y = 119.500000}, + {.point.x = 67.855469, .point.y = 159.640625}, + {.point.x = 71.281250, .point.y = 172.000000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 73.156250, .point.y = 178.757812}, + {.point.x = 74.492188, .point.y = 184.515625}, + {.point.x = 74.218750, .point.y = 184.750000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 73.949219, .point.y = 184.984375}, + {.point.x = 68.496094, .point.y = 183.968750}, + {.point.x = 62.093750, .point.y = 182.500000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 47.703125, .point.y = 179.199219}, + {.point.x = 20.855469, .point.y = 178.828125}, + {.point.x = 8.875000, .point.y = 181.812500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 0.000000, .point.y = 184.031250}, + {.header.type = 1, .header.length = 2}, + {.point.x = 6.218750, .point.y = 186.750000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 15.660156, .point.y = 190.910156}, + {.point.x = 29.242188, .point.y = 204.425781}, + {.point.x = 34.906250, .point.y = 215.218750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 42.023438, .point.y = 228.773438}, + {.point.x = 47.375000, .point.y = 247.988281}, + {.point.x = 50.562500, .point.y = 271.718750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 54.019531, .point.y = 297.421875}, + {.point.x = 54.898438, .point.y = 301.527344}, + {.point.x = 58.312500, .point.y = 309.187500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 62.550781, .point.y = 318.691406}, + {.point.x = 74.937500, .point.y = 334.589844}, + {.point.x = 86.937500, .point.y = 345.843750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 96.863281, .point.y = 355.152344}, + {.point.x = 97.121094, .point.y = 355.523438}, + {.point.x = 90.031250, .point.y = 350.781250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 85.796875, .point.y = 347.949219}, + {.point.x = 81.859375, .point.y = 345.039062}, + {.point.x = 81.312500, .point.y = 344.281250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 80.769531, .point.y = 343.527344}, + {.point.x = 77.796875, .point.y = 341.339844}, + {.point.x = 74.687500, .point.y = 339.437500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 69.062500, .point.y = 336.000000}, + {.header.type = 1, .header.length = 2}, + {.point.x = 70.343750, .point.y = 343.656250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 71.976562, .point.y = 353.304688}, + {.point.x = 81.210938, .point.y = 377.386719}, + {.point.x = 85.375000, .point.y = 382.843750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 89.117188, .point.y = 387.804688}, + {.point.x = 92.617188, .point.y = 393.371094}, + {.point.x = 96.406250, .point.y = 398.937500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 140.062500, .point.y = 398.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 125.261719, .point.y = 391.414062}, + {.point.x = 111.375000, .point.y = 383.683594}, + {.point.x = 111.375000, .point.y = 382.625000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 111.375000, .point.y = 382.187500}, + {.point.x = 116.660156, .point.y = 384.617188}, + {.point.x = 123.156250, .point.y = 388.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 130.234375, .point.y = 391.750000}, + {.point.x = 137.937500, .point.y = 395.398438}, + {.point.x = 146.062500, .point.y = 398.937500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 247.531250, .point.y = 398.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 212.316406, .point.y = 389.019531}, + {.point.x = 176.621094, .point.y = 375.136719}, + {.point.x = 146.812500, .point.y = 359.125000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 124.667969, .point.y = 347.230469}, + {.point.x = 121.132812, .point.y = 343.796875}, + {.point.x = 141.375000, .point.y = 353.843750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 181.539062, .point.y = 373.777344}, + {.point.x = 230.722656, .point.y = 390.035156}, + {.point.x = 277.125000, .point.y = 398.937500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 387.281250, .point.y = 398.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 389.449219, .point.y = 398.289062}, + {.point.x = 391.582031, .point.y = 397.632812}, + {.point.x = 393.406250, .point.y = 397.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 402.406250, .point.y = 394.074219}, + {.point.x = 403.734375, .point.y = 393.250000}, + {.point.x = 408.781250, .point.y = 384.656250}, + {.header.type = 1, .header.length = 2}, + {.point.x = 408.781250, .point.y = 369.218750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 354.609375, .point.y = 380.550781}, + {.point.x = 261.796875, .point.y = 362.976562}, + {.point.x = 186.843750, .point.y = 326.812500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 170.699219, .point.y = 319.023438}, + {.point.x = 149.531250, .point.y = 307.312500}, + {.point.x = 149.531250, .point.y = 306.156250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 149.531250, .point.y = 305.832031}, + {.point.x = 156.691406, .point.y = 309.121094}, + {.point.x = 165.437500, .point.y = 313.468750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 208.855469, .point.y = 335.046875}, + {.point.x = 265.519531, .point.y = 352.527344}, + {.point.x = 313.187500, .point.y = 359.093750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 333.339844, .point.y = 361.871094}, + {.point.x = 369.632812, .point.y = 362.246094}, + {.point.x = 385.093750, .point.y = 359.812500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 391.878906, .point.y = 358.746094}, + {.point.x = 401.050781, .point.y = 356.160156}, + {.point.x = 408.781250, .point.y = 353.437500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 408.781250, .point.y = 327.812500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 357.781250, .point.y = 339.546875}, + {.point.x = 260.570312, .point.y = 318.058594}, + {.point.x = 187.781250, .point.y = 278.000000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 172.882812, .point.y = 269.800781}, + {.point.x = 170.656250, .point.y = 267.132812}, + {.point.x = 184.531250, .point.y = 274.093750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 225.066406, .point.y = 294.429688}, + {.point.x = 273.574219, .point.y = 309.937500}, + {.point.x = 316.531250, .point.y = 316.281250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 339.093750, .point.y = 319.613281}, + {.point.x = 375.757812, .point.y = 320.351562}, + {.point.x = 389.531250, .point.y = 317.718750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 395.226562, .point.y = 316.632812}, + {.point.x = 402.355469, .point.y = 314.566406}, + {.point.x = 408.781250, .point.y = 312.343750}, + {.header.type = 1, .header.length = 2}, + {.point.x = 408.781250, .point.y = 286.781250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 392.652344, .point.y = 290.996094}, + {.point.x = 359.242188, .point.y = 291.675781}, + {.point.x = 331.593750, .point.y = 286.687500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 293.644531, .point.y = 279.839844}, + {.point.x = 248.632812, .point.y = 264.089844}, + {.point.x = 217.218750, .point.y = 246.687500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 202.500000, .point.y = 238.531250}, + {.point.x = 200.261719, .point.y = 235.210938}, + {.point.x = 214.593750, .point.y = 242.781250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 228.839844, .point.y = 250.308594}, + {.point.x = 256.128906, .point.y = 260.804688}, + {.point.x = 277.156250, .point.y = 266.843750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 323.738281, .point.y = 280.226562}, + {.point.x = 366.109375, .point.y = 283.082031}, + {.point.x = 394.843750, .point.y = 274.781250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 399.859375, .point.y = 273.332031}, + {.point.x = 404.953125, .point.y = 271.539062}, + {.point.x = 408.781250, .point.y = 269.937500}, + {.header.type = 1, .header.length = 2}, + {.point.x = 408.781250, .point.y = 246.375000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 405.968750, .point.y = 241.371094}, + {.point.x = 403.597656, .point.y = 237.246094}, + {.point.x = 403.343750, .point.y = 237.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 403.019531, .point.y = 236.753906}, + {.point.x = 398.386719, .point.y = 238.460938}, + {.point.x = 393.031250, .point.y = 240.843750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 383.820312, .point.y = 244.945312}, + {.point.x = 382.070312, .point.y = 245.160156}, + {.point.x = 359.625000, .point.y = 245.093750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 340.851562, .point.y = 245.039062}, + {.point.x = 332.679688, .point.y = 244.292969}, + {.point.x = 320.437500, .point.y = 241.468750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 293.203125, .point.y = 235.187500}, + {.point.x = 255.355469, .point.y = 220.000000}, + {.point.x = 242.218750, .point.y = 210.062500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 238.386719, .point.y = 207.164062}, + {.point.x = 238.996094, .point.y = 207.246094}, + {.point.x = 246.812500, .point.y = 210.812500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 279.453125, .point.y = 225.703125}, + {.point.x = 308.761719, .point.y = 233.277344}, + {.point.x = 338.656250, .point.y = 234.531250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 357.054688, .point.y = 235.304688}, + {.point.x = 360.980469, .point.y = 234.976562}, + {.point.x = 371.375000, .point.y = 231.968750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 389.828125, .point.y = 226.628906}, + {.point.x = 390.781250, .point.y = 225.835938}, + {.point.x = 391.156250, .point.y = 215.093750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 391.449219, .point.y = 206.785156}, + {.point.x = 390.515625, .point.y = 204.261719}, + {.point.x = 382.312500, .point.y = 190.468750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 377.289062, .point.y = 182.019531}, + {.point.x = 373.121094, .point.y = 173.703125}, + {.point.x = 373.031250, .point.y = 171.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 372.945312, .point.y = 170.171875}, + {.point.x = 371.769531, .point.y = 171.984375}, + {.point.x = 370.468750, .point.y = 176.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 363.761719, .point.y = 196.949219}, + {.point.x = 349.835938, .point.y = 203.890625}, + {.point.x = 323.812500, .point.y = 199.250000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 309.765625, .point.y = 196.746094}, + {.point.x = 278.191406, .point.y = 183.371094}, + {.point.x = 280.875000, .point.y = 181.062500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 281.234375, .point.y = 180.757812}, + {.point.x = 286.589844, .point.y = 182.289062}, + {.point.x = 292.781250, .point.y = 184.437500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 313.656250, .point.y = 191.675781}, + {.point.x = 335.207031, .point.y = 191.050781}, + {.point.x = 345.625000, .point.y = 182.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 353.035156, .point.y = 177.167969}, + {.point.x = 355.558594, .point.y = 171.070312}, + {.point.x = 355.437500, .point.y = 159.343750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 355.097656, .point.y = 126.226562}, + {.point.x = 321.609375, .point.y = 89.011719}, + {.point.x = 275.187500, .point.y = 70.156250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 265.082031, .point.y = 66.050781}, + {.point.x = 241.316406, .point.y = 58.656250}, + {.point.x = 238.187500, .point.y = 58.656250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 238.132812, .point.y = 58.656250}, + {.point.x = 238.070312, .point.y = 58.636719}, + {.point.x = 238.031250, .point.y = 58.656250}, + {.header.type = 3, .header.length = 1}, + {.header.type = 0, .header.length = 2}, + {.point.x = 388.437500, .point.y = 132.843750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 387.890625, .point.y = 132.968750}, + {.point.x = 386.785156, .point.y = 133.718750}, + {.point.x = 385.156250, .point.y = 135.031250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 374.984375, .point.y = 143.238281}, + {.point.x = 375.207031, .point.y = 153.203125}, + {.point.x = 385.968750, .point.y = 167.937500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 388.792969, .point.y = 171.800781}, + {.point.x = 392.011719, .point.y = 177.000000}, + {.point.x = 393.125000, .point.y = 179.531250}, + {.header.type = 1, .header.length = 2}, + {.point.x = 395.156250, .point.y = 184.156250}, + {.header.type = 1, .header.length = 2}, + {.point.x = 398.718750, .point.y = 179.531250}, + {.header.type = 2, .header.length = 4}, + {.point.x = 403.707031, .point.y = 173.039062}, + {.point.x = 403.257812, .point.y = 162.636719}, + {.point.x = 397.656250, .point.y = 153.875000}, + {.header.type = 2, .header.length = 4}, + {.point.x = 389.765625, .point.y = 141.527344}, + {.point.x = 387.507812, .point.y = 136.390625}, + {.point.x = 388.656250, .point.y = 133.812500}, + {.header.type = 2, .header.length = 4}, + {.point.x = 388.929688, .point.y = 133.203125}, + {.point.x = 388.921875, .point.y = 132.882812}, + {.point.x = 388.656250, .point.y = 132.843750}, + {.header.type = 2, .header.length = 4}, + {.point.x = 388.601562, .point.y = 132.835938}, + {.point.x = 388.519531, .point.y = 132.828125}, + {.point.x = 388.437500, .point.y = 132.843750}, + {.header.type = 3, .header.length = 1}, + {.header.type = 0, .header.length = 2}, + {.point.x = 388.437500, .point.y = 132.843750} +}; + +cairo_path_t glade_path = {0, glade_path_data, 397}; +#endif /* __glade_path_H__ */ diff --git a/gladeui/glade-placeholder.c b/gladeui/glade-placeholder.c new file mode 100644 index 0000000..1263b2a --- /dev/null +++ b/gladeui/glade-placeholder.c @@ -0,0 +1,689 @@ +/* + * Copyright (C) 2003, 2004 Joaquin Cuenca Abela + * + * Authors: + * Joaquin Cuenca Abela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +*/ + +#include "config.h" + +/** + * SECTION:glade-placeholder + * @Short_Description: A #GtkWidget to fill empty places. + * + * Generally in Glade, container widgets are implemented with #GladePlaceholder + * children to allow users to 'click' and add their widgets to a container. + * It is the responsibility of the plugin writer to create placeholders for + * container widgets where appropriate; usually in #GladePostCreateFunc + * when the #GladeCreateReason is %GLADE_CREATE_USER. + */ + +#include +#include "glade-marshallers.h" +#include "glade.h" +#include "glade-placeholder.h" +#include "glade-xml-utils.h" +#include "glade-project.h" +#include "glade-command.h" +#include "glade-palette.h" +#include "glade-popup.h" +#include "glade-cursor.h" +#include "glade-widget.h" +#include "glade-app.h" +#include "glade-adaptor-chooser-widget.h" +#include + +#include "glade-dnd.h" +#include "glade-drag.h" + +#define WIDTH_REQUISITION 20 +#define HEIGHT_REQUISITION 20 + +static cairo_pattern_t *placeholder_pattern = NULL; + +struct _GladePlaceholderPrivate +{ + GList *packing_actions; + + GdkWindow *event_window; + + gboolean drag_highlight; +}; + +enum +{ + PROP_0, + PROP_HADJUSTMENT, + PROP_VADJUSTMENT, + PROP_HSCROLL_POLICY, + PROP_VSCROLL_POLICY +}; + +#define GLADE_PLACEHOLDER_PRIVATE(object) (((GladePlaceholder*)object)->priv) + +static void glade_placeholder_drag_init (_GladeDragInterface *iface); + +G_DEFINE_TYPE_WITH_CODE (GladePlaceholder, glade_placeholder, GTK_TYPE_WIDGET, + G_ADD_PRIVATE (GladePlaceholder) + G_IMPLEMENT_INTERFACE (GTK_TYPE_SCROLLABLE, NULL) + G_IMPLEMENT_INTERFACE (GLADE_TYPE_DRAG, + glade_placeholder_drag_init)) + +static void +glade_placeholder_notify_parent (GObject *gobject, + GParamSpec *arg1, + gpointer user_data) +{ + GladePlaceholder *placeholder = GLADE_PLACEHOLDER (gobject); + GladeWidgetAdaptor *parent_adaptor = NULL; + GladeWidget *parent = glade_placeholder_get_parent (placeholder); + + if (placeholder->priv->packing_actions) + { + g_list_free_full (placeholder->priv->packing_actions, g_object_unref); + placeholder->priv->packing_actions = NULL; + } + + if (parent) + parent_adaptor = glade_widget_get_adaptor (parent); + + if (parent_adaptor) + placeholder->priv->packing_actions = + glade_widget_adaptor_pack_actions_new (parent_adaptor); +} + +static void +glade_placeholder_init (GladePlaceholder *placeholder) +{ + placeholder->priv = glade_placeholder_get_instance_private (placeholder); + + placeholder->priv->packing_actions = NULL; + + gtk_widget_set_can_focus (GTK_WIDGET (placeholder), TRUE); + gtk_widget_set_has_window (GTK_WIDGET (placeholder), FALSE); + + gtk_widget_set_size_request (GTK_WIDGET (placeholder), + WIDTH_REQUISITION, HEIGHT_REQUISITION); + + _glade_dnd_dest_set (GTK_WIDGET (placeholder)); + + g_signal_connect (placeholder, "notify::parent", + G_CALLBACK (glade_placeholder_notify_parent), NULL); + + gtk_widget_set_hexpand (GTK_WIDGET (placeholder), TRUE); + gtk_widget_set_vexpand (GTK_WIDGET (placeholder), TRUE); + gtk_widget_show (GTK_WIDGET (placeholder)); +} + +static void +glade_placeholder_finalize (GObject *object) +{ + GladePlaceholder *placeholder; + + g_return_if_fail (GLADE_IS_PLACEHOLDER (object)); + placeholder = GLADE_PLACEHOLDER (object); + + if (placeholder->priv->packing_actions) + g_list_free_full (placeholder->priv->packing_actions, g_object_unref); + + G_OBJECT_CLASS (glade_placeholder_parent_class)->finalize (object); +} + +static void +glade_placeholder_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + + switch (prop_id) + { + case PROP_HADJUSTMENT: + case PROP_VADJUSTMENT: + case PROP_HSCROLL_POLICY: + case PROP_VSCROLL_POLICY: + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_placeholder_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_HADJUSTMENT: + case PROP_VADJUSTMENT: + g_value_set_object (value, NULL); + break; + case PROP_HSCROLL_POLICY: + case PROP_VSCROLL_POLICY: + g_value_set_enum (value, GTK_SCROLL_MINIMUM); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_placeholder_realize (GtkWidget *widget) +{ + GladePlaceholder *placeholder; + GtkAllocation allocation; + GdkWindow *window; + GdkWindowAttr attributes; + gint attributes_mask; + + placeholder = GLADE_PLACEHOLDER (widget); + + gtk_widget_set_realized (widget, TRUE); + + gtk_widget_get_allocation (widget, &allocation); + attributes.x = allocation.x; + attributes.y = allocation.y; + attributes.width = allocation.width; + attributes.height = allocation.height; + + attributes.window_type = GDK_WINDOW_CHILD; + attributes.wclass = GDK_INPUT_ONLY; + attributes.event_mask = + gtk_widget_get_events (widget) | + GDK_POINTER_MOTION_MASK | + GDK_POINTER_MOTION_HINT_MASK | + GDK_BUTTON_PRESS_MASK | + GDK_BUTTON_RELEASE_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK; + attributes_mask = GDK_WA_X | GDK_WA_Y; + + window = gtk_widget_get_parent_window (widget); + gtk_widget_set_window (widget, g_object_ref (window)); + + placeholder->priv->event_window = + gdk_window_new (gtk_widget_get_parent_window (widget), &attributes, + attributes_mask); + gdk_window_set_user_data (placeholder->priv->event_window, widget); +} + +static void +glade_placeholder_unrealize (GtkWidget *widget) +{ + GladePlaceholder *placeholder; + + placeholder = GLADE_PLACEHOLDER (widget); + + if (placeholder->priv->event_window) + { + gdk_window_set_user_data (placeholder->priv->event_window, NULL); + gdk_window_destroy (placeholder->priv->event_window); + placeholder->priv->event_window = NULL; + } + + GTK_WIDGET_CLASS (glade_placeholder_parent_class)->unrealize (widget); +} + +static void +glade_placeholder_map (GtkWidget *widget) +{ + GladePlaceholder *placeholder; + + placeholder = GLADE_PLACEHOLDER (widget); + + if (placeholder->priv->event_window) + { + gdk_window_show (placeholder->priv->event_window); + } + + GTK_WIDGET_CLASS (glade_placeholder_parent_class)->map (widget); +} + +static void +glade_placeholder_unmap (GtkWidget *widget) +{ + GladePlaceholder *placeholder; + + placeholder = GLADE_PLACEHOLDER (widget); + + if (placeholder->priv->event_window) + { + gdk_window_hide (placeholder->priv->event_window); + } + + GTK_WIDGET_CLASS (glade_placeholder_parent_class)->unmap (widget); +} + +static void +glade_placeholder_size_allocate (GtkWidget *widget, GtkAllocation *allocation) +{ + GladePlaceholder *placeholder; + + placeholder = GLADE_PLACEHOLDER (widget); + + gtk_widget_set_allocation (widget, allocation); + + if (gtk_widget_get_realized (widget)) + { + gdk_window_move_resize (placeholder->priv->event_window, + allocation->x, allocation->y, + allocation->width, allocation->height); + } +} + +static gboolean +glade_placeholder_draw (GtkWidget *widget, cairo_t *cr) +{ + GladePlaceholder *placeholder = GLADE_PLACEHOLDER (widget); + gint h = gtk_widget_get_allocated_height (widget) - 1; + gint w = gtk_widget_get_allocated_width (widget) - 1; + + if (placeholder_pattern) + { + cairo_save (cr); + cairo_rectangle (cr, 0, 0, w, h); + cairo_set_source (cr, placeholder_pattern); + cairo_fill (cr); + cairo_restore (cr); + } + + cairo_translate (cr, .5, .5); + cairo_set_line_width (cr, 1.0); + + /* We hardcode colors here since we are already using an image as bg pattern */ + cairo_set_source_rgb (cr, .9, .9, .9); + cairo_move_to (cr, w, 0); + cairo_line_to (cr, 0, 0); + cairo_line_to (cr, 0, h); + cairo_stroke (cr); + + cairo_set_source_rgb (cr, .64, .64, .64); + cairo_move_to (cr, w, 0); + cairo_line_to (cr, w, h); + cairo_line_to (cr, 0, h); + cairo_stroke (cr); + + if (placeholder->priv->drag_highlight) + { + cairo_pattern_t *gradient; + GtkStyleContext *context; + gdouble ww, hh; + GdkRGBA c; + + context = gtk_widget_get_style_context (widget); + gtk_style_context_save (context); + gtk_style_context_get_background_color (context, + gtk_style_context_get_state (context) | + GTK_STATE_FLAG_SELECTED | + GTK_STATE_FLAG_FOCUSED, &c); + gtk_style_context_restore (context); + + ww = w/2.0; + hh = h/2.0; + gradient = cairo_pattern_create_radial (ww, hh, MIN (w, h)/6, + ww, hh, MAX (ww, hh)); + cairo_pattern_add_color_stop_rgba (gradient, 0, c.red, c.green, c.blue, .08); + cairo_pattern_add_color_stop_rgba (gradient, 1, c.red, c.green, c.blue, .28); + + cairo_set_source (cr, gradient); + + cairo_rectangle (cr, 0, 0, w, h); + cairo_fill (cr); + + cairo_pattern_destroy (gradient); + } + + return FALSE; +} + +static void +glade_placeholder_update_cursor (GladePlaceholder *placeholder, GdkWindow *win) +{ + GladeProject *project = glade_placeholder_get_project (placeholder); + GladePointerMode pointer_mode = glade_project_get_pointer_mode (project); + + if (pointer_mode == GLADE_POINTER_SELECT) + glade_cursor_set (project, win, GLADE_CURSOR_SELECTOR); + else if (pointer_mode == GLADE_POINTER_ADD_WIDGET) + glade_cursor_set (project, win, GLADE_CURSOR_ADD_WIDGET); +} + +static gboolean +glade_placeholder_enter_notify_event (GtkWidget *widget, + GdkEventCrossing *event) +{ + glade_placeholder_update_cursor (GLADE_PLACEHOLDER (widget), event->window); + return FALSE; +} + +static gboolean +glade_placeholder_motion_notify_event (GtkWidget *widget, GdkEventMotion *event) +{ + glade_placeholder_update_cursor (GLADE_PLACEHOLDER (widget), event->window); + return FALSE; +} + +static void +on_chooser_adaptor_widget_selected (_GladeAdaptorChooserWidget *chooser, + GladeWidgetAdaptor *adaptor, + GladePlaceholder *placeholder) + +{ + glade_command_create (adaptor, glade_placeholder_get_parent (placeholder), + placeholder, glade_placeholder_get_project (placeholder)); + gtk_widget_destroy (gtk_widget_get_ancestor (GTK_WIDGET (chooser), GTK_TYPE_POPOVER)); +} + +static GtkWidget * +glade_placeholder_popover_new (GladePlaceholder *placeholder, GtkWidget *relative_to) +{ + GtkWidget *pop = gtk_popover_new (relative_to); + GtkWidget *chooser; + + chooser = _glade_adaptor_chooser_widget_new (GLADE_ADAPTOR_CHOOSER_WIDGET_WIDGET | + GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_TOPLEVEL | + GLADE_ADAPTOR_CHOOSER_WIDGET_SKIP_DEPRECATED, + glade_placeholder_get_project (placeholder)); + _glade_adaptor_chooser_widget_populate (GLADE_ADAPTOR_CHOOSER_WIDGET (chooser)); + g_signal_connect (chooser, "adaptor-selected", + G_CALLBACK (on_chooser_adaptor_widget_selected), + placeholder); + gtk_popover_set_position (GTK_POPOVER (pop), GTK_POS_BOTTOM); + gtk_container_add (GTK_CONTAINER (pop), chooser); + gtk_widget_show (chooser); + + return pop; +} + +static gboolean +glade_placeholder_button_press (GtkWidget *widget, GdkEventButton *event) +{ + GladePlaceholder *placeholder; + GladeProject *project; + GladeWidgetAdaptor *adaptor; + gboolean handled = FALSE; + + g_return_val_if_fail (GLADE_IS_PLACEHOLDER (widget), FALSE); + + placeholder = GLADE_PLACEHOLDER (widget); + project = glade_placeholder_get_project (placeholder); + adaptor = glade_project_get_add_item (project); + + if (!gtk_widget_has_focus (widget)) + gtk_widget_grab_focus (widget); + + if ((event->button == 1 || event->button == 2) && + event->type == GDK_BUTTON_PRESS && adaptor != NULL) + { + GladeWidget *parent = glade_placeholder_get_parent (placeholder); + + /* A widget type is selected in the palette. + * Add a new widget of that type. + */ + glade_command_create (adaptor, parent, placeholder, project); + + /* Let the user use the middle button to create more than one widget */ + if (event->button != 2) + { + glade_project_set_add_item (project, NULL); + /* reset the cursor */ + glade_project_set_pointer_mode (project, GLADE_POINTER_SELECT); + } + + handled = TRUE; + } + else if (event->button == 1 && + event->type == GDK_2BUTTON_PRESS && + adaptor == NULL) + { + GtkWidget *event_widget = gtk_get_event_widget ((GdkEvent*) event); + GladeWidget *toplevel = glade_widget_get_toplevel (glade_placeholder_get_parent (placeholder)); + GtkWidget *parent = gtk_widget_get_parent (GTK_WIDGET (glade_widget_get_object (toplevel))); + GtkWidget *pop = glade_placeholder_popover_new (placeholder, parent); + GdkRectangle rect = {0, 0, 8, 8}; + + gtk_widget_translate_coordinates (event_widget, parent, + event->x, event->y, + &rect.x, &rect.y); + gtk_popover_set_pointing_to (GTK_POPOVER (pop), &rect); + gtk_popover_popup (GTK_POPOVER (pop)); + handled = TRUE; + } + + if (!handled && glade_popup_is_popup_event (event)) + { + glade_popup_placeholder_pop (placeholder, event); + handled = TRUE; + } + + return handled; +} + +static gboolean +glade_placeholder_popup_menu (GtkWidget *widget) +{ + g_return_val_if_fail (GLADE_IS_PLACEHOLDER (widget), FALSE); + + glade_popup_placeholder_pop (GLADE_PLACEHOLDER (widget), NULL); + + return TRUE; +} + +static gboolean +glade_placeholder_drag_can_drag (_GladeDrag *source) +{ + GladeWidget *parent = glade_placeholder_get_parent (GLADE_PLACEHOLDER (source)); + return (parent) ? _glade_drag_can_drag (GLADE_DRAG (parent)) : FALSE; +} + +static gboolean +glade_placeholder_drag_can_drop (_GladeDrag *dest, gint x, gint y, GObject *data) +{ + if (GLADE_IS_WIDGET_ADAPTOR (data)) + { + GType otype = glade_widget_adaptor_get_object_type (GLADE_WIDGET_ADAPTOR (data)); + + if (g_type_is_a (otype, GTK_TYPE_WIDGET) && !GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (data)) + return TRUE; + } + else if (GTK_IS_WIDGET (data)) + { + GladeWidget *parent, *new_child; + + /* Avoid recursion */ + if (gtk_widget_is_ancestor (GTK_WIDGET (dest), GTK_WIDGET (data))) + return FALSE; + + parent = glade_placeholder_get_parent (GLADE_PLACEHOLDER (dest)); + + if ((new_child = glade_widget_get_from_gobject (data)) && + !glade_widget_add_verify (parent, new_child, FALSE)) + return FALSE; + + return TRUE; + } + + return FALSE; +} + +static gboolean +glade_placeholder_drag_drop (_GladeDrag *dest, gint x, gint y, GObject *data) +{ + GladePlaceholder *placeholder = GLADE_PLACEHOLDER (dest); + GladeWidget *gsource; + + if (!data) + return FALSE; + + if (GLADE_IS_WIDGET_ADAPTOR (data)) + { + GladeWidget *parent = glade_placeholder_get_parent (placeholder); + + glade_command_create (GLADE_WIDGET_ADAPTOR (data), parent, placeholder, + glade_widget_get_project (parent)); + return TRUE; + } + else if ((gsource = glade_widget_get_from_gobject (data))) + { + GladeWidget *parent = glade_placeholder_get_parent (placeholder); + GList widgets = {gsource, NULL, NULL}; + + /* Check for recursive paste */ + if (parent != gsource) + { + glade_command_dnd (&widgets, parent, placeholder); + return TRUE; + } + } + + return FALSE; +} + +static void +glade_placeholder_drag_highlight (_GladeDrag *dest, gint x, gint y) +{ + GladePlaceholderPrivate *priv = GLADE_PLACEHOLDER (dest)->priv; + gboolean highlight = !(x < 0 || y < 0); + + if (priv->drag_highlight == highlight) + return; + + priv->drag_highlight = highlight; + gtk_widget_queue_draw (GTK_WIDGET (dest)); +} + +static void +glade_placeholder_drag_init (_GladeDragInterface *iface) +{ + iface->can_drag = glade_placeholder_drag_can_drag; + iface->can_drop = glade_placeholder_drag_can_drop; + iface->drop = glade_placeholder_drag_drop; + iface->highlight = glade_placeholder_drag_highlight; +} + +static void +glade_placeholder_class_init (GladePlaceholderClass *klass) +{ + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); + GObjectClass *object_class = G_OBJECT_CLASS (klass); + gchar *path; + cairo_surface_t *surface; + + object_class->finalize = glade_placeholder_finalize; + object_class->set_property = glade_placeholder_set_property; + object_class->get_property = glade_placeholder_get_property; + + widget_class->realize = glade_placeholder_realize; + widget_class->unrealize = glade_placeholder_unrealize; + widget_class->map = glade_placeholder_map; + widget_class->unmap = glade_placeholder_unmap; + widget_class->size_allocate = glade_placeholder_size_allocate; + widget_class->draw = glade_placeholder_draw; + widget_class->enter_notify_event = glade_placeholder_enter_notify_event; + widget_class->motion_notify_event = glade_placeholder_motion_notify_event; + widget_class->button_press_event = glade_placeholder_button_press; + widget_class->popup_menu = glade_placeholder_popup_menu; + + /* GtkScrollable implementation */ + g_object_class_override_property (object_class, PROP_HADJUSTMENT, + "hadjustment"); + g_object_class_override_property (object_class, PROP_VADJUSTMENT, + "vadjustment"); + g_object_class_override_property (object_class, PROP_HSCROLL_POLICY, + "hscroll-policy"); + g_object_class_override_property (object_class, PROP_VSCROLL_POLICY, + "vscroll-policy"); + + /* Create our tiled background pattern */ + path = g_build_filename (glade_app_get_pixmaps_dir (), "placeholder.png", NULL); + surface = cairo_image_surface_create_from_png (path); + + if (!surface) + g_warning ("Failed to create surface for %s\n", path); + else + { + placeholder_pattern = cairo_pattern_create_for_surface (surface); + cairo_pattern_set_extend (placeholder_pattern, CAIRO_EXTEND_REPEAT); + } + g_free (path); +} + +/** + * glade_placeholder_new: + * + * Returns: (transfer full): a new #GladePlaceholder + */ +GtkWidget * +glade_placeholder_new (void) +{ + return g_object_new (GLADE_TYPE_PLACEHOLDER, NULL); +} + +/** + * glade_placeholder_get_project: + * @placeholder: a #GladePlaceholder + * + * Returns: (transfer none) (nullable): a #GladeProject + */ +GladeProject * +glade_placeholder_get_project (GladePlaceholder *placeholder) +{ + GladeWidget *parent; + parent = glade_placeholder_get_parent (placeholder); + return parent ? glade_widget_get_project (parent) : NULL; +} + +/** + * glade_placeholder_get_parent: + * @placeholder: a #GladePlaceholder + * + * Returns: (transfer none) (nullable): a #GladeProject + */ +GladeWidget * +glade_placeholder_get_parent (GladePlaceholder *placeholder) +{ + GtkWidget *widget; + GladeWidget *parent = NULL; + + g_return_val_if_fail (GLADE_IS_PLACEHOLDER (placeholder), NULL); + + for (widget = gtk_widget_get_parent (GTK_WIDGET (placeholder)); + widget != NULL; widget = gtk_widget_get_parent (widget)) + { + if ((parent = glade_widget_get_from_gobject (widget)) != NULL) + break; + } + return parent; +} + +/** + * glade_placeholder_packing_actions: + * @placeholder: a #GladePlaceholder + * + * Returns: (transfer none) (element-type GladeWidgetAction): a list of #GladeWidgetAction + */ +GList * +glade_placeholder_packing_actions (GladePlaceholder *placeholder) +{ + g_return_val_if_fail (GLADE_IS_PLACEHOLDER (placeholder), NULL); + + return placeholder->priv->packing_actions; +} + diff --git a/gladeui/glade-placeholder.h b/gladeui/glade-placeholder.h new file mode 100644 index 0000000..9cb29bd --- /dev/null +++ b/gladeui/glade-placeholder.h @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2003, 2004 Joaquin Cuenca Abela + * + * Authors: + * Joaquin Cuenca Abela + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * +*/ + +#ifndef __GLADE_PLACEHOLDER_H__ +#define __GLADE_PLACEHOLDER_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PLACEHOLDER (glade_placeholder_get_type ()) +#define GLADE_PLACEHOLDER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PLACEHOLDER, GladePlaceholder)) +#define GLADE_PLACEHOLDER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PLACEHOLDER, GladePlaceholderClass)) +#define GLADE_IS_PLACEHOLDER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PLACEHOLDER)) +#define GLADE_IS_PLACEHOLDER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PLACEHOLDER)) +#define GLADE_PLACEHOLDER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PLACEHOLDER, GladePlaceholderClass)) + +typedef struct _GladePlaceholder GladePlaceholder; +typedef struct _GladePlaceholderClass GladePlaceholderClass; +typedef struct _GladePlaceholderPrivate GladePlaceholderPrivate; + +struct _GladePlaceholder +{ + GtkWidget widget; + + GladePlaceholderPrivate *priv; +}; + +struct _GladePlaceholderClass +{ + GtkWidgetClass parent_class; + + void (* glade_reserved1) (void); + void (* glade_reserved2) (void); + void (* glade_reserved3) (void); + void (* glade_reserved4) (void); +}; + + +GType glade_placeholder_get_type (void) G_GNUC_CONST; + +GtkWidget *glade_placeholder_new (void); +GladeProject *glade_placeholder_get_project (GladePlaceholder *placeholder); +GladeWidget *glade_placeholder_get_parent (GladePlaceholder *placeholder); +GList *glade_placeholder_packing_actions (GladePlaceholder *placeholder); + +G_END_DECLS + +#endif /* __GLADE_PLACEHOLDER_H__ */ diff --git a/gladeui/glade-popup.c b/gladeui/glade-popup.c new file mode 100644 index 0000000..2862163 --- /dev/null +++ b/gladeui/glade-popup.c @@ -0,0 +1,680 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Chema Celorio + * Tristan Van Berkom + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-widget-adaptor.h" +#include "glade-popup.h" +#include "glade-placeholder.h" +#include "glade-clipboard.h" +#include "glade-command.h" +#include "glade-project.h" +#include "glade-app.h" + +static void +glade_popup_docs_cb (GtkMenuItem *item, GladeWidgetAdaptor *adaptor) +{ + g_return_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor)); + + glade_app_search_docs (glade_widget_adaptor_get_book (adaptor), + glade_widget_adaptor_get_display_name (adaptor), + NULL); +} + +/******************************************************** + WIDGET POPUP + *******************************************************/ +static void +glade_popup_select_cb (GtkMenuItem *item, GladeWidget *widget) +{ + glade_project_selection_set (glade_widget_get_project (widget), + glade_widget_get_object (widget), TRUE); +} + +typedef struct { + GladeWidgetAdaptor *adaptor; + GladeProject *project; + GladeWidget *parent; + GladePlaceholder *placeholder; +} RootAddData; + +static void +glade_popup_widget_add_cb (GtkMenuItem *item, RootAddData *data) +{ + g_return_if_fail (data->adaptor != NULL); + + if (glade_command_create (data->adaptor, data->parent, + data->placeholder, data->project)) + + glade_project_set_add_item (data->project, NULL); +} + +static void +glade_popup_root_add_cb (GtkMenuItem *item, RootAddData *data) +{ + if (glade_command_create (data->adaptor, NULL, NULL, data->project)) + glade_project_set_add_item (data->project, NULL); +} + +static void +glade_popup_cut_cb (GtkMenuItem *item, GladeWidget *widget) +{ + GladeProject *project = glade_widget_get_project (widget); + + /* Assign selection first only if its not already assigned (it may be a delete + * of multiple widgets) */ + if (!glade_project_is_selected (project, glade_widget_get_object (widget))) + glade_project_selection_set (project, glade_widget_get_object (widget), FALSE); + + glade_project_command_cut (project); +} + +static void +glade_popup_copy_cb (GtkMenuItem *item, GladeWidget *widget) +{ + GladeProject *project = glade_widget_get_project (widget); + + /* Assign selection first */ + if (!glade_project_is_selected (project, glade_widget_get_object (widget))) + glade_project_selection_set (project, glade_widget_get_object (widget), FALSE); + + glade_project_copy_selection (project); +} + +static void +glade_popup_paste_cb (GtkMenuItem *item, gpointer data) +{ + GladeWidget *widget = NULL; + GladeProject *project; + + if (GLADE_IS_WIDGET (data)) + { + widget = GLADE_WIDGET (data); + project = glade_widget_get_project (widget); + } + else if (GLADE_IS_PROJECT (data)) + project = GLADE_PROJECT (data); + else + g_return_if_reached (); + + /* The selected widget is the paste destination */ + if (widget) + glade_project_selection_set (project, glade_widget_get_object (widget), FALSE); + else + glade_project_selection_clear (project, FALSE); + + glade_project_command_paste (project, NULL); +} + +static void +glade_popup_delete_cb (GtkMenuItem *item, GladeWidget *widget) +{ + GladeProject *project = glade_widget_get_project (widget); + + /* Assign selection first */ + if (glade_project_is_selected + (project, glade_widget_get_object (widget)) == FALSE) + glade_project_selection_set (project, glade_widget_get_object (widget), FALSE); + + glade_project_command_delete (project); +} + +/******************************************************** + PLACEHOLDER POPUP + *******************************************************/ +static void +glade_popup_placeholder_paste_cb (GtkMenuItem *item, + GladePlaceholder *placeholder) +{ + GladeProject *project; + + project = glade_placeholder_get_project (placeholder); + + glade_project_selection_clear (project, FALSE); + + glade_project_command_paste (project, placeholder); +} + +/******************************************************** + POPUP BUILDING + *******************************************************/ +static GtkWidget * +glade_popup_append_item (GtkWidget *popup_menu, + const gchar *label, + gboolean sensitive, + gpointer callback, + gpointer data) +{ + GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic (label); + + if (callback) + g_signal_connect (G_OBJECT (menu_item), "activate", + G_CALLBACK (callback), data); + + gtk_widget_set_sensitive (menu_item, sensitive); + gtk_widget_show (menu_item); + gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), menu_item); + + return menu_item; +} + + +static void +glade_popup_menuitem_activated (GtkMenuItem *item, const gchar *action_path) +{ + GladeWidget *widget; + + if ((widget = g_object_get_data (G_OBJECT (item), "gwa-data"))) + glade_widget_adaptor_action_activate (glade_widget_get_adaptor (widget), + glade_widget_get_object (widget), action_path); +} + +static void +glade_popup_menuitem_packing_activated (GtkMenuItem *item, + const gchar *action_path) +{ + GladeWidget *widget, *parent; + + if ((widget = g_object_get_data (G_OBJECT (item), "gwa-data"))) + { + parent = glade_widget_get_parent (widget); + glade_widget_adaptor_child_action_activate (glade_widget_get_adaptor (parent), + glade_widget_get_object (parent), + glade_widget_get_object (widget), action_path); + } +} + +static void +glade_popup_menuitem_ph_packing_activated (GtkMenuItem *item, + const gchar *action_path) +{ + GladePlaceholder *ph; + GladeWidget *parent; + + if ((ph = g_object_get_data (G_OBJECT (item), "gwa-data"))) + { + parent = glade_placeholder_get_parent (ph); + glade_widget_adaptor_child_action_activate (glade_widget_get_adaptor (parent), + glade_widget_get_object (parent), + G_OBJECT (ph), action_path); + } +} + +static gint +glade_popup_action_populate_menu_real (GtkWidget *menu, + GladeWidget *gwidget, + GList *actions, + GCallback callback, + gpointer data) +{ + GtkWidget *item; + GList *list; + gint n = 0; + + for (list = actions; list; list = g_list_next (list)) + { + GladeWidgetAction *action = list->data; + GladeWidgetActionDef *adef = glade_widget_action_get_def (action); + GList *children = glade_widget_action_get_children (action); + GtkWidget *submenu = NULL; + + if (!glade_widget_action_get_visible (action)) + continue; + + if (children) + { + submenu = gtk_menu_new (); + n += glade_popup_action_populate_menu_real (submenu, + gwidget, + children, + callback, data); + } + else + submenu = glade_widget_adaptor_action_submenu (glade_widget_get_adaptor (gwidget), + glade_widget_get_object (gwidget), + adef->path); + + + item = glade_popup_append_item (menu, adef->label, TRUE, + (children) ? NULL : callback, + (children) ? NULL : adef->path); + + g_object_set_data (G_OBJECT (item), "gwa-data", data); + + gtk_widget_set_sensitive (item, glade_widget_action_get_sensitive (action)); + + if (submenu) + gtk_menu_item_set_submenu (GTK_MENU_ITEM (item), submenu); + + n++; + } + + return n; +} + +/* + * glade_popup_action_populate_menu: + * @menu: a GtkMenu to put the actions menu items. + * @widget: A #GladeWidget + * @action: a @widget subaction or NULL to include all actions. + * @packing: TRUE to include packing actions + * + * Populate a GtkMenu with widget's actions + * + * Returns the number of action appended to the menu. + */ +gint +glade_popup_action_populate_menu (GtkWidget *menu, + GladeWidget *widget, + GladeWidgetAction *action, + gboolean packing) +{ + gint n; + + g_return_val_if_fail (GTK_IS_MENU (menu), 0); + g_return_val_if_fail (GLADE_IS_WIDGET (widget), 0); + + g_return_val_if_fail (action == NULL || GLADE_IS_WIDGET_ACTION (action), 0); + + if (action) + { + GladeWidgetActionDef *adef = glade_widget_action_get_def (action); + GList *children = glade_widget_action_get_children (action); + + if (glade_widget_get_action (widget, adef->path) && + glade_widget_action_get_visible (action)) + return glade_popup_action_populate_menu_real (menu, + widget, + children, + G_CALLBACK + (glade_popup_menuitem_activated), + widget); + + if (glade_widget_get_pack_action (widget, adef->path) && + glade_widget_action_get_visible (action)) + return glade_popup_action_populate_menu_real (menu, + glade_widget_get_parent + (widget), children, + G_CALLBACK + (glade_popup_menuitem_packing_activated), + widget); + + return 0; + } + + n = glade_popup_action_populate_menu_real (menu, + widget, + glade_widget_get_actions (widget), + G_CALLBACK + (glade_popup_menuitem_activated), + widget); + + if (packing && glade_widget_get_pack_actions (widget)) + { + if (n) + { + GtkWidget *separator = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), separator); + gtk_widget_show (separator); + } + + n += glade_popup_action_populate_menu_real + (menu, glade_widget_get_parent (widget), + glade_widget_get_pack_actions (widget), + G_CALLBACK (glade_popup_menuitem_packing_activated), widget); + } + + return n; +} + +static GtkWidget * +glade_popup_create_menu (GladeWidget *widget, + GladePlaceholder *placeholder, + GladeProject *project, + gboolean packing) +{ + GtkWidget *popup_menu; + GtkWidget *separator; + gboolean sensitive; + GladeWidgetAdaptor *adaptor; + + popup_menu = gtk_menu_new (); + + adaptor = glade_project_get_add_item (project); + + if (adaptor) + { + RootAddData *data = g_new (RootAddData, 1); + + data->adaptor = adaptor; + data->project = project; + data->parent = placeholder ? glade_placeholder_get_parent (placeholder) : widget; + data->placeholder = placeholder; + + g_object_set_data_full (G_OBJECT (popup_menu), "root-data-destroy-me", + data, (GDestroyNotify)g_free); + + glade_popup_append_item (popup_menu, _("_Add widget here"), + data->parent != NULL, + glade_popup_widget_add_cb, + data); + + glade_popup_append_item (popup_menu, _("Add widget as _toplevel"), TRUE, + glade_popup_root_add_cb, data); + + separator = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), separator); + gtk_widget_show (separator); + } + + sensitive = (widget != NULL); + + glade_popup_append_item (popup_menu, _("_Select"), sensitive, + glade_popup_select_cb, widget); + glade_popup_append_item (popup_menu, _("Cu_t"), sensitive, + glade_popup_cut_cb, widget); + glade_popup_append_item (popup_menu, _("_Copy"), sensitive, + glade_popup_copy_cb, widget); + + /* paste is placholder specific when the popup is on a placeholder */ + sensitive = glade_clipboard_get_has_selection (glade_app_get_clipboard ()); + + if (placeholder) + glade_popup_append_item (popup_menu, _("_Paste"), sensitive, + glade_popup_placeholder_paste_cb, placeholder); + else if (widget) + glade_popup_append_item (popup_menu, _("_Paste"), sensitive, + glade_popup_paste_cb, widget); + else + glade_popup_append_item (popup_menu, _("_Paste"), sensitive, + glade_popup_paste_cb, NULL); + + + glade_popup_append_item (popup_menu, _("_Delete"), (widget != NULL), + glade_popup_delete_cb, widget); + + + /* packing actions are a little different on placholders */ + if (placeholder) + { + if (widget && glade_widget_get_actions (widget)) + { + GtkWidget *separator = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), separator); + gtk_widget_show (separator); + + glade_popup_action_populate_menu_real + (popup_menu, + widget, + glade_widget_get_actions (widget), + G_CALLBACK (glade_popup_menuitem_activated), widget); + } + + if (glade_placeholder_packing_actions (placeholder)) + { + GtkWidget *separator = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), separator); + gtk_widget_show (separator); + + glade_popup_action_populate_menu_real + (popup_menu, + widget, + glade_placeholder_packing_actions (placeholder), + G_CALLBACK (glade_popup_menuitem_ph_packing_activated), + placeholder); + } + } + else if (widget && (glade_widget_get_actions (widget) || + (packing && glade_widget_get_pack_actions (widget)))) + { + GtkWidget *separator = gtk_separator_menu_item_new (); + gtk_menu_shell_append (GTK_MENU_SHELL (popup_menu), separator); + gtk_widget_show (separator); + + glade_popup_action_populate_menu (popup_menu, widget, NULL, packing); + } + + return popup_menu; +} + +void +glade_popup_widget_pop (GladeWidget *widget, + GdkEventButton *event, + gboolean packing) +{ + GtkWidget *popup_menu; + gint button; + gint event_time; + + g_return_if_fail (GLADE_IS_WIDGET (widget) || widget == NULL); + + popup_menu = glade_popup_create_menu (widget, NULL, glade_widget_get_project (widget), packing); + + if (event) + { + button = event->button; + event_time = event->time; + } + else + { + button = 0; + event_time = gtk_get_current_event_time (); + } + gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, + NULL, NULL, button, event_time); +} + +void +glade_popup_placeholder_pop (GladePlaceholder *placeholder, + GdkEventButton *event) +{ + GladeWidget *widget; + GtkWidget *popup_menu; + gint button; + gint event_time; + + g_return_if_fail (GLADE_IS_PLACEHOLDER (placeholder)); + + widget = glade_placeholder_get_parent (placeholder); + + popup_menu = glade_popup_create_menu (widget, placeholder, + glade_widget_get_project (widget), TRUE); + + if (event) + { + button = event->button; + event_time = event->time; + } + else + { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, + NULL, NULL, button, event_time); +} + +void +glade_popup_palette_pop (GladePalette *palette, + GladeWidgetAdaptor *adaptor, + GdkEventButton *event) +{ + GladeProject *project; + GtkWidget *popup_menu; + gint button; + gint event_time; + RootAddData *data; + + g_return_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor)); + + popup_menu = gtk_menu_new (); + + project = glade_palette_get_project (palette); + + data = g_new (RootAddData, 1); + data->adaptor = adaptor; + data->project = project; + g_object_set_data_full (G_OBJECT (popup_menu), "root-data-destroy-me", + data, (GDestroyNotify)g_free); + + glade_popup_append_item (popup_menu, _("Add widget as _toplevel"), TRUE, + glade_popup_root_add_cb, data); + + if (glade_widget_adaptor_get_book (adaptor) && glade_util_have_devhelp ()) + glade_popup_append_item (popup_menu, _("Read _documentation"), TRUE, + glade_popup_docs_cb, adaptor); + + if (event) + { + button = event->button; + event_time = event->time; + } + else + { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, + NULL, NULL, button, event_time); +} + +static void +glade_popup_clear_property_cb (GtkMenuItem *item, GladeProperty *property) +{ + GValue value = { 0, }; + + glade_property_get_default (property, &value); + glade_command_set_property_value (property, &value); + g_value_unset (&value); +} + +static void +glade_popup_property_docs_cb (GtkMenuItem *item, GladeProperty *property) +{ + GladeWidgetAdaptor *adaptor, *prop_adaptor; + GladePropertyDef *pdef; + GParamSpec *pspec; + gchar *search; + + pdef = glade_property_get_def (property); + pspec = glade_property_def_get_pspec (pdef); + prop_adaptor = glade_property_def_get_adaptor (pdef); + adaptor = glade_widget_adaptor_from_pspec (prop_adaptor, pspec); + search = g_strdup_printf ("The \"%s\" property", glade_property_def_id (pdef)); + + glade_app_search_docs (glade_widget_adaptor_get_book (adaptor), + g_type_name (pspec->owner_type), search); + + g_free (search); +} + +void +glade_popup_property_pop (GladeProperty *property, GdkEventButton *event) +{ + + GladeWidgetAdaptor *adaptor, *prop_adaptor; + GladePropertyDef *pdef; + GParamSpec *pspec; + GtkWidget *popup_menu; + gint button; + gint event_time; + + pdef = glade_property_get_def (property); + pspec = glade_property_def_get_pspec (pdef); + prop_adaptor = glade_property_def_get_adaptor (pdef); + adaptor = glade_widget_adaptor_from_pspec (prop_adaptor, pspec); + + g_return_if_fail (GLADE_IS_WIDGET_ADAPTOR (adaptor)); + + popup_menu = gtk_menu_new (); + + glade_popup_append_item (popup_menu, _("Set default value"), TRUE, + glade_popup_clear_property_cb, property); + + if (!glade_property_def_get_virtual (pdef) && + glade_widget_adaptor_get_book (adaptor) && + glade_util_have_devhelp ()) + { + glade_popup_append_item (popup_menu, _("Read _documentation"), TRUE, + glade_popup_property_docs_cb, property); + } + + if (event) + { + button = event->button; + event_time = event->time; + } + else + { + button = 0; + event_time = gtk_get_current_event_time (); + } + + gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, + NULL, NULL, button, event_time); +} + +void +glade_popup_simple_pop (GladeProject *project, GdkEventButton *event) +{ + GtkWidget *popup_menu; + gint button; + gint event_time; + + popup_menu = glade_popup_create_menu (NULL, NULL, project, FALSE); + if (!popup_menu) + return; + + if (event) + { + button = event->button; + event_time = event->time; + } + else + { + button = 0; + event_time = gtk_get_current_event_time (); + } + gtk_menu_popup (GTK_MENU (popup_menu), NULL, NULL, + NULL, NULL, button, event_time); +} + +gboolean +glade_popup_is_popup_event (GdkEventButton *event) +{ + g_return_val_if_fail (event, FALSE); + +#ifdef __APPLE__ + return (event->type == GDK_BUTTON_PRESS && event->button == 1 && + ((event->state & GDK_MOD1_MASK) != 0)); +#else + return (event->type == GDK_BUTTON_PRESS && event->button == 3); +#endif +} diff --git a/gladeui/glade-popup.h b/gladeui/glade-popup.h new file mode 100644 index 0000000..8306af4 --- /dev/null +++ b/gladeui/glade-popup.h @@ -0,0 +1,36 @@ +#ifndef __GLADE_POPUP_H__ +#define __GLADE_POPUP_H__ + +G_BEGIN_DECLS + +void glade_popup_widget_pop (GladeWidget *widget, + GdkEventButton *event, + gboolean packing); + +void glade_popup_placeholder_pop (GladePlaceholder *placeholder, + GdkEventButton *event); + +void glade_popup_clipboard_pop (GladeWidget *widget, + GdkEventButton *event); + +void glade_popup_palette_pop (GladePalette *palette, + GladeWidgetAdaptor *adaptor, + GdkEventButton *event); + +gint glade_popup_action_populate_menu (GtkWidget *menu, + GladeWidget *widget, + GladeWidgetAction *action, + gboolean packing); + +void glade_popup_simple_pop (GladeProject *project, + GdkEventButton *event); + +void glade_popup_property_pop (GladeProperty *property, + GdkEventButton *event); + + +gboolean glade_popup_is_popup_event (GdkEventButton *event); + +G_END_DECLS + +#endif /* __GLADE_POPUP_H__ */ diff --git a/gladeui/glade-preview-template.c b/gladeui/glade-preview-template.c new file mode 100644 index 0000000..b7d18a9 --- /dev/null +++ b/gladeui/glade-preview-template.c @@ -0,0 +1,301 @@ +/* + * glade-preview-template.c + * + * Copyright (C) 2013 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include + +#include +#include "glade-utils.h" +#include "glade-preview-template.h" + +typedef struct +{ + GTypeInfo info; + GString *template_string; + GBytes *template_data; + + GtkBuilderConnectFunc connect_func; + gpointer connect_data; + gint count; +} TypeData; + +/* We need to call gtk_widget_init_template() in the instance init for the +* template to work. +*/ +static void +template_init (GTypeInstance *instance, gpointer g_class) +{ + gtk_widget_init_template (GTK_WIDGET (instance)); +} + +static void +template_connect_function (GtkBuilder *builder, + GObject *object, + const gchar *signal_name, + const gchar *handler_name, + GObject *connect_object, + GConnectFlags flags, + gpointer data) +{ + /* Ignore signal connections */ +} + +/* Need to associate the class with a template */ +static void +template_class_init (gpointer g_class, gpointer user_data) +{ + TypeData *data = user_data; + + gtk_widget_class_set_template (g_class, data->template_data); + + if (data->connect_func && data->connect_data) + gtk_widget_class_set_connect_func (g_class, data->connect_func, data->connect_data, NULL); + else + gtk_widget_class_set_connect_func (g_class, template_connect_function, NULL, NULL); +} + +static GQuark type_data_quark = 0; + +static GType +template_generate_type (const gchar *name, + const gchar *parent_name, + GString *template_string, + GtkBuilderConnectFunc connect_func, + gpointer connect_data) +{ + GType parent_type, retval; + gchar *real_name = NULL; + GTypeQuery query; + TypeData *data; + + g_return_val_if_fail (name != NULL, 0); + g_return_val_if_fail (parent_name != NULL, 0); + + parent_type = glade_util_get_type_from_name (parent_name, FALSE); + g_return_val_if_fail (parent_type != 0, 0); + + if ((retval = g_type_from_name (name)) && + (data = g_type_get_qdata (retval, type_data_quark))) + { + /* Type already registered! reuse TypeData + * + * If the template and parent class are the same there is no need to + * register a new type + */ + if (g_type_parent (retval) == parent_type && + template_string->len == data->template_string->len && + g_strcmp0 (template_string->str, data->template_string->str) == 0) + return retval; + + real_name = g_strdup_printf ("GladePreviewTemplate_%s_%d", name, data->count); + } + else + { + /* We only allocate a TypeData struct once for each type class */ + data = g_new0 (TypeData, 1); + } + + g_type_query (parent_type, &query); + g_return_val_if_fail (query.type != 0, 0); + + /* Free old template string */ + if (data->template_string) + g_string_free (data->template_string, TRUE); + + /* And old bytes reference to template string */ + if (data->template_data) + g_bytes_unref (data->template_data); + + /* Take ownership, will be freed next time we want to create this type */ + data->template_string = template_string; + + data->info.class_size = query.class_size; + data->info.instance_size = query.instance_size; + data->info.class_init = template_class_init; + data->info.instance_init = template_init; + data->info.class_data = data; + data->template_data = g_bytes_new_static (template_string->str, template_string->len); + data->connect_func = connect_func; + data->connect_data = connect_data; + + retval = g_type_register_static (parent_type, real_name ? real_name : name, &data->info, 0); + + /* bind TypeData struct with GType */ + if (!data->count) + g_type_set_qdata (retval, type_data_quark, data); + + data->count++; + + g_free (real_name); + + return retval; +} + +typedef struct +{ + gboolean is_template; + GString *xml; + gchar *klass, *parent; + gint indent; +} ParseData; + +static void +start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + ParseData *state = user_data; + gboolean is_template = FALSE; + gint i; + + g_string_append_printf (state->xml, "<%s", element_name); + + if (g_strcmp0 (element_name, "template") == 0) + state->is_template = is_template = TRUE; + + for (i = 0; attribute_names[i]; i++) + { + gchar *escaped_value = g_markup_escape_text (attribute_values[i], -1); + + if (is_template) + { + if (!g_strcmp0 (attribute_names[i], "class")) + { + TypeData *data; + GType type; + + state->klass = g_strdup (attribute_values[i]); + + /* If we are in a template then we need to replace the class with + * the fake name template_generate_type() will use to register + * a new class + */ + if ((type = g_type_from_name (state->klass)) && + (data = g_type_get_qdata (type, type_data_quark))) + { + g_free (escaped_value); + escaped_value = g_strdup_printf ("GladePreviewTemplate_%s_%d", state->klass, data->count); + } + } + else if (!g_strcmp0 (attribute_names[i], "parent")) + state->parent = g_strdup (attribute_values[i]); + } + + g_string_append_printf (state->xml, " %s=\"%s\"", + attribute_names[i], escaped_value); + g_free (escaped_value); + } + + g_string_append (state->xml, ">"); +} + +static void +end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ + ParseData *state = user_data; + g_string_append_printf (state->xml, "", element_name); +} + +static void +text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + gchar *escaped_text = g_markup_escape_text (text, text_len); + ParseData *state = user_data; + + g_string_append_len (state->xml, escaped_text, -1); + + g_free (escaped_text); +} + +static void +passthrough (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + ParseData *state = user_data; + g_string_append_len (state->xml, text, text_len); + g_string_append_c (state->xml, '\n'); +} + +GObject * +glade_preview_template_object_new (const gchar *template_data, + gsize len, + GtkBuilderConnectFunc connect_func, + gpointer connect_data) +{ + GMarkupParser parser = { start_element, end_element, text, passthrough}; + ParseData state = { FALSE, NULL}; + GMarkupParseContext *context; + GObject *object = NULL; + + if (type_data_quark == 0) + type_data_quark = g_quark_from_string ("glade-preview-type-data"); + + if (len == -1) + len = strlen (template_data); + + /* Preallocate enough for the string plus the new fake template name */ + state.xml = g_string_sized_new (len + 32); + + context = g_markup_parse_context_new (&parser, + G_MARKUP_TREAT_CDATA_AS_TEXT | + G_MARKUP_PREFIX_ERROR_POSITION, + &state, NULL); + + if (g_markup_parse_context_parse (context, template_data, len, NULL) && + g_markup_parse_context_end_parse (context, NULL) && + state.is_template) + { + GType template_type = template_generate_type (state.klass, + state.parent, + state.xml, + connect_func, + connect_data); + if (template_type) + object = g_object_new (template_type, NULL); + else + { + /* on success template_generate_type() takes ownership of xml */ + g_string_free (state.xml, TRUE); + } + } + else + g_string_free (state.xml, TRUE); + + g_free (state.klass); + g_free (state.parent); + g_markup_parse_context_free (context); + + return object ? g_object_ref_sink (object) : NULL; +} diff --git a/gladeui/glade-preview-template.h b/gladeui/glade-preview-template.h new file mode 100644 index 0000000..14cd759 --- /dev/null +++ b/gladeui/glade-preview-template.h @@ -0,0 +1,39 @@ +/* + * glade-preview-template.h + * + * Copyright (C) 2013 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _GLADE_PREVIEW_TEMPLATE_H_ +#define _GLADE_PREVIEW_TEMPLATE_H_ + +#include + +G_BEGIN_DECLS + +GObject * +glade_preview_template_object_new (const gchar *template_data, + gsize len, + GtkBuilderConnectFunc connect_func, + gpointer connect_data); + +G_END_DECLS + +#endif /* _GLADE_PREVIEW_TEMPLATE_H_ */ diff --git a/gladeui/glade-preview-tokens.h b/gladeui/glade-preview-tokens.h new file mode 100644 index 0000000..f131a8e --- /dev/null +++ b/gladeui/glade-preview-tokens.h @@ -0,0 +1,11 @@ +#ifndef __GLADE_PREVIEW_TOKENS_H__ +#define __GLADE_PREVIEW_TOKENS_H__ + +#define UPDATE_TOKEN "\n" +#define UPDATE_TOKEN_SIZE strlen (UPDATE_TOKEN) + +#define QUIT_TOKEN "\n" +#define QUIT_TOKEN_SIZE strlen (QUIT_TOKEN) + +#endif /* __GLADE_PREVIEW_TOKENS_H__ */ + diff --git a/gladeui/glade-preview.c b/gladeui/glade-preview.c new file mode 100644 index 0000000..00e834b --- /dev/null +++ b/gladeui/glade-preview.c @@ -0,0 +1,362 @@ +/* + * Copyright (C) 2010 Marco Diego Aurélio Mesquita + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Marco Diego Aurélio Mesquita + */ + +#include + +/** + * SECTION:glade-preview + * @Short_Description: The glade preview launch/kill interface. + * + * This object owns all data that is needed to keep a preview. It stores + * the GIOChannel used for communication between glade and glade-previewer, + * the event source id for a watch (in the case a watch is used to monitor + * the communication channel), the previewed widget and the pid of the + * corresponding glade-previewer. + * + */ + + +#include +#include +#include +#include +#include + +#include "glade.h" +#include "glade-preview.h" +#include "glade-project.h" +#include "glade-app.h" + +#include "glade-preview-tokens.h" + +#ifdef G_OS_WIN32 +#define GLADE_PREVIEWER "glade-previewer.exe" +#else +#define GLADE_PREVIEWER "glade-previewer" +#endif + +/* Private data for glade-preview */ +struct _GladePreviewPrivate +{ + GIOChannel *channel; /* Channel user for communication between glade and glade-previewer */ + guint watch; /* Event source id used to monitor the channel */ + GladeWidget *previewed_widget; + GPid pid; /* Pid of the corresponding glade-previewer process */ +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GladePreview, glade_preview, G_TYPE_OBJECT) + +enum +{ + PREVIEW_EXITS, + LAST_SIGNAL +}; + +static guint glade_preview_signals[LAST_SIGNAL] = { 0 }; + +/** + * glade_preview_kill + * @preview: a #GladePreview that will be killed. + * + * Uses the communication channel and protocol to send the "" token to the + * glade-previewer telling it to commit suicide. + * + */ +static void +glade_preview_kill (GladePreview *preview) +{ + const gchar *quit = QUIT_TOKEN; + GIOChannel *channel; + GError *error = NULL; + gsize size; + + channel = preview->priv->channel; + g_io_channel_write_chars (channel, quit, strlen (quit), &size, &error); + + if (size != strlen (quit) && error != NULL) + { + g_warning ("Error passing quit signal trough pipe: %s", error->message); + g_error_free (error); + } + + g_io_channel_flush (channel, &error); + if (error != NULL) + { + g_warning ("Error flushing channel: %s", error->message); + g_error_free (error); + } + + g_io_channel_shutdown (channel, TRUE, &error); + if (error != NULL) + { + g_warning ("Error shutting down channel: %s", error->message); + g_error_free (error); + } +} + +static void +glade_preview_dispose (GObject *gobject) +{ + GladePreview *self = GLADE_PREVIEW (gobject); + + if (self->priv->watch) + { + g_source_remove (self->priv->watch); + glade_preview_kill (self); + } + + if (self->priv->channel) + { + g_io_channel_unref (self->priv->channel); + self->priv->channel = NULL; + } + + G_OBJECT_CLASS (glade_preview_parent_class)->dispose (gobject); +} + +/* We have to use finalize because of the signal that is sent in dispose */ +static void +glade_preview_finalize (GObject *gobject) +{ + G_OBJECT_CLASS (glade_preview_parent_class)->finalize (gobject); +} + +static void +glade_preview_class_init (GladePreviewClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->dispose = glade_preview_dispose; + gobject_class->finalize = glade_preview_finalize; + + /** + * GladePreview::exits: + * @gladepreview: the #GladePreview which received the signal. + * @gladeproject: the #GladeProject associated with the preview. + * + * Emitted when @preview exits. + */ + glade_preview_signals[PREVIEW_EXITS] = + g_signal_new ("exits", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); +} + +static void +glade_preview_init (GladePreview *self) +{ + GladePreviewPrivate *priv; + + self->priv = priv = glade_preview_get_instance_private (self); + priv->channel = NULL; +} + +static void +glade_preview_internal_watch (GPid pid, gint status, gpointer data) +{ + GladePreview *preview = GLADE_PREVIEW (data); + + preview->priv->watch = 0; + + /* This means a preview exited. We'll now signal it */ + g_signal_emit (preview, glade_preview_signals[PREVIEW_EXITS], 0); +} + +/** + * glade_preview_launch: + * @widget: Pointer to a local instance of the widget that will be previewed. + * @buffer: Contents of an xml definition of the interface which will be previewed + * + * Creates a new #GladePreview and launches glade-previewer to preview it. + * + * Returns: a new #GladePreview or NULL if launch fails. + * + */ +GladePreview * +glade_preview_launch (GladeWidget *widget, const gchar *buffer) +{ + GPid pid; + GError *error = NULL; + gchar *argv[10], *executable; + gint child_stdin; + gsize bytes_written; + GIOChannel *output; + GladePreview *preview = NULL; + const gchar *css_provider, *filename; + GladeProject *project; + gchar *name; + gint i; + + g_return_val_if_fail (GLADE_IS_WIDGET (widget), NULL); + + executable = g_find_program_in_path (GLADE_PREVIEWER); + + project = glade_widget_get_project (widget); + filename = glade_project_get_path (project); + name = (filename) ? NULL : glade_project_get_name (project); + + argv[0] = executable; + argv[1] = "--listen"; + argv[2] = "--toplevel"; + argv[3] = (gchar *) glade_widget_get_name (widget); + argv[4] = "--filename"; + argv[5] = (filename) ? (gchar *) filename : name; + + i = 5; + if (glade_project_get_template (project)) + argv[++i] = "--template"; + + argv[++i] = NULL; + + css_provider = glade_project_get_css_provider_path (glade_widget_get_project (widget)); + if (css_provider) + { + argv[i] = "--css"; + argv[++i] = (gchar *) css_provider; + argv[++i] = NULL; + } + + if (g_spawn_async_with_pipes (NULL, + argv, + NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, + &pid, &child_stdin, NULL, NULL, + &error) == FALSE) + { + g_warning (_("Error launching previewer: %s\n"), error->message); + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_ERROR, NULL, + _("Failed to launch preview: %s.\n"), + error->message); + g_error_free (error); + g_free (executable); + g_free (name); + return NULL; + } + +#ifdef G_OS_WIN32 + output = g_io_channel_win32_new_fd (child_stdin); +#else + output = g_io_channel_unix_new (child_stdin); +#endif + + g_io_channel_write_chars (output, buffer, strlen (buffer), &bytes_written, + &error); + + if (bytes_written != strlen (buffer) && error != NULL) + { + g_warning ("Error passing UI trough pipe: %s", error->message); + g_error_free (error); + } + + g_io_channel_flush (output, &error); + if (error != NULL) + { + g_warning ("Error flushing UI trough pipe: %s", error->message); + g_error_free (error); + } + + /* Setting up preview data */ + preview = g_object_new (GLADE_TYPE_PREVIEW, NULL); + preview->priv->channel = output; + preview->priv->previewed_widget = widget; + preview->priv->pid = pid; + + preview->priv->watch = + g_child_watch_add (preview->priv->pid, + glade_preview_internal_watch, + preview); + + g_free (executable); + g_free (name); + + return preview; +} + +void +glade_preview_update (GladePreview *preview, const gchar *buffer) +{ + const gchar *update_token = UPDATE_TOKEN; + gchar *update; + GIOChannel *channel; + GError *error = NULL; + gsize size; + gsize bytes_written; + GladeWidget *gwidget; + + g_return_if_fail (GLADE_IS_PREVIEW (preview)); + g_return_if_fail (buffer && buffer[0]); + + gwidget = glade_preview_get_widget (preview); + + update = g_strdup_printf ("%s%s\n", update_token, + glade_widget_get_name (gwidget)); + + channel = preview->priv->channel; + g_io_channel_write_chars (channel, update, strlen (update), &size, &error); + + if (size != strlen (update) && error != NULL) + { + g_warning ("Error passing quit signal trough pipe: %s", error->message); + g_error_free (error); + } + + g_io_channel_flush (channel, &error); + if (error != NULL) + { + g_warning ("Error flushing channel: %s", error->message); + g_error_free (error); + } + + /* We'll now send the interface: */ + g_io_channel_write_chars (channel, buffer, strlen (buffer), &bytes_written, + &error); + + if (bytes_written != strlen (buffer) && error != NULL) + { + g_warning ("Error passing UI trough pipe: %s", error->message); + g_error_free (error); + } + + g_io_channel_flush (channel, &error); + if (error != NULL) + { + g_warning ("Error flushing UI trough pipe: %s", error->message); + g_error_free (error); + } + + g_free (update); +} + +GladeWidget * +glade_preview_get_widget (GladePreview *preview) +{ + g_return_val_if_fail (GLADE_IS_PREVIEW (preview), NULL); + return preview->priv->previewed_widget; +} + +GPid +glade_preview_get_pid (GladePreview *preview) +{ + g_return_val_if_fail (GLADE_IS_PREVIEW (preview), 0); + return preview->priv->pid; +} diff --git a/gladeui/glade-preview.h b/gladeui/glade-preview.h new file mode 100644 index 0000000..8656335 --- /dev/null +++ b/gladeui/glade-preview.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 Marco Diego Aurélio Mesquita + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Marco Diego Aurélio Mesquita + */ + +#ifndef _GLADE_PREVIEW_H_ +#define _GLADE_PREVIEW_H_ + +#include +#include +#include + +G_BEGIN_DECLS +#define GLADE_TYPE_PREVIEW (glade_preview_get_type ()) +#define GLADE_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PREVIEW, GladePreview)) +#define GLADE_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PREVIEW, GladePreviewClass)) +#define GLADE_IS_PREVIEW(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PREVIEW)) +#define GLADE_IS_PREVIEW_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PREVIEW)) +#define GLADE_PREVIEW_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PREVIEW, GladePreviewClass)) +typedef struct _GladePreview GladePreview; +typedef struct _GladePreviewClass GladePreviewClass; +typedef struct _GladePreviewPrivate GladePreviewPrivate; + +struct _GladePreview +{ + GObject parent_instance; + + GladePreviewPrivate *priv; +}; + +struct _GladePreviewClass +{ + GObjectClass parent_class; +}; + +GType glade_preview_get_type (void) G_GNUC_CONST; +GladePreview *glade_preview_launch (GladeWidget *widget, + const gchar *buffer); +void glade_preview_update (GladePreview *preview, + const gchar *buffer); +GladeWidget *glade_preview_get_widget (GladePreview *preview); +GPid glade_preview_get_pid (GladePreview *preview); + +G_END_DECLS + +#endif /* _GLADE_PREVIEW_H_ */ diff --git a/gladeui/glade-previewer-main.c b/gladeui/glade-previewer-main.c new file mode 100644 index 0000000..5dab4ac --- /dev/null +++ b/gladeui/glade-previewer-main.c @@ -0,0 +1,447 @@ +/* + * Copyright (C) 2010 Marco Diego Aurélio Mesquita + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Marco Diego Aurélio Mesquita + */ + +#include + +#include + +#include +#include +#include +#include + +#include "glade-previewer.h" +#include "glade-preview-template.h" +#include "glade-preview-tokens.h" + +typedef struct +{ + GladePreviewer *preview; + gchar *file_name, *toplevel; + gboolean is_template; +} GladePreviewerApp; + +static GObject * +get_toplevel (GtkBuilder *builder, gchar *name) +{ + GObject *toplevel = NULL; + GObject *object; + + if (name == NULL) + { + GSList *l, *objects = gtk_builder_get_objects (builder); + + /* Iterate trough objects and search for a window or widget */ + for (l = objects; l; l = g_slist_next (l)) + { + GObject *obj = l->data; + + if (!GTK_IS_WIDGET (obj) || gtk_widget_get_parent (GTK_WIDGET (obj))) + continue; + + if (toplevel == NULL) + toplevel = obj; + else if (GTK_IS_WINDOW (obj)) + toplevel = obj; + } + + g_slist_free (objects); + if (toplevel == NULL) + { + g_printerr (_("UI definition has no previewable widgets.\n")); + exit (1); + } + } + else + { + object = gtk_builder_get_object (builder, name); + + if (object == NULL) + { + g_printerr (_("Object %s not found in UI definition.\n"), name); + exit (1); + } + + if (!GTK_IS_WIDGET (object)) + { + g_printerr (_("Object is not previewable.\n")); + exit (1); + } + + toplevel = object; + } + + return g_object_ref_sink (toplevel); +} + +static GObject * +get_toplevel_from_string (GladePreviewerApp *app, gchar *name, gchar *string, gsize size) +{ + gchar *wd = NULL; + GObject *retval = NULL; + + /* We need to change the working directory so builder get a chance to load resources */ + if (app->file_name) + { + gchar *dirname = g_path_get_dirname (app->file_name); + wd = g_get_current_dir (); + g_chdir (dirname); + g_free (dirname); + } + + /* We use template flag as a hint since the user can turn on and off template + * while the preview is live. + */ + if (app->is_template) + retval = glade_preview_template_object_new (string, size, + glade_previewer_connect_function, + app->preview); + + if (!retval) + { + GtkBuilder *builder = gtk_builder_new (); + GError *error = NULL; + + /* We do not know if its a template yet */ + app->is_template = FALSE; + + if (gtk_builder_add_from_string (builder, string, size, &error)) + { + gtk_builder_connect_signals_full (builder, + glade_previewer_connect_function, + app->preview); + retval = get_toplevel (builder, name); + } + else + { + if (error->code == GTK_BUILDER_ERROR_UNHANDLED_TAG && + (retval = glade_preview_template_object_new (string, size, + glade_previewer_connect_function, + app->preview))) + { + /* At this point we know it is a template, so keep a hint for next time */ + app->is_template = TRUE; + } + else + { + gchar *message = g_strdup_printf (_("Couldn't load builder definition: %s"), error->message); + glade_previewer_set_message (app->preview, GTK_MESSAGE_ERROR, message); + g_free (message); + } + + g_error_free (error); + } + + g_object_unref (builder); + } + + /* restore directory */ + if (wd) + { + g_chdir (wd); + g_free (wd); + } + + return retval; +} + +static gchar * +read_buffer (GIOChannel * source) +{ + gchar *buffer; + gchar *token; + gchar *tmp; + GError *error = NULL; + + if (g_io_channel_read_line (source, &token, NULL, NULL, &error) != + G_IO_STATUS_NORMAL) + { + g_printerr (_("Error: %s.\n"), error->message); + g_error_free (error); + exit (1); + } + + /* Check for quit token */ + if (g_strcmp0 (QUIT_TOKEN, token) == 0) + { + g_free (token); + return NULL; + } + + /* Loop to load the UI */ + buffer = g_strdup (token); + do + { + g_free (token); + if (g_io_channel_read_line (source, &token, NULL, NULL, &error) != + G_IO_STATUS_NORMAL) + { + g_printerr (_("Error: %s.\n"), error->message); + g_error_free (error); + exit (1); + } + tmp = buffer; + buffer = g_strconcat (buffer, token, NULL); + g_free (tmp); + } + while (g_strcmp0 ("\n", token) != 0); + g_free (token); + + return buffer; +} + +static gboolean +on_data_incoming (GIOChannel *source, GIOCondition condition, gpointer data) +{ + GladePreviewerApp *app = data; + GObject *new_widget; + gchar *buffer; + + buffer = read_buffer (source); + if (buffer == NULL) + { + gtk_main_quit (); + return FALSE; + } + + if (condition & G_IO_HUP) + { + g_printerr (_("Broken pipe!\n")); + exit (1); + } + + /* We have an update */ + if (g_str_has_prefix (buffer, UPDATE_TOKEN)) + { + gchar **split_buffer = g_strsplit_set (buffer + UPDATE_TOKEN_SIZE, "\n", 2); + + if (!split_buffer) + { + g_free (buffer); + return FALSE; + } + + new_widget = get_toplevel_from_string (app, split_buffer[0], split_buffer[1], -1); + + g_strfreev (split_buffer); + } + else + { + new_widget = get_toplevel_from_string (app, app->toplevel, buffer, -1); + } + + if (new_widget) + { + glade_previewer_set_widget (app->preview, GTK_WIDGET (new_widget)); + gtk_widget_show (GTK_WIDGET (new_widget)); + } + + glade_previewer_present (app->preview); + + g_free (buffer); + + return TRUE; +} + +static GladePreviewerApp * +glade_previewer_app_new (gchar *filename, gchar *toplevel) +{ + GladePreviewerApp *app = g_new0 (GladePreviewerApp, 1); + + app->preview = GLADE_PREVIEWER (glade_previewer_new ()); + g_object_ref_sink (app->preview); + + app->file_name = g_strdup (filename); + app->toplevel = g_strdup (toplevel); + + return app; +} + +static void +glade_previewer_free (GladePreviewerApp *app) +{ + g_object_unref (app->preview); + g_free (app->file_name); + g_free (app->toplevel); + g_free (app); +} + +static gboolean listen = FALSE; +static gboolean version = FALSE; +static gboolean slideshow = FALSE; +static gboolean template = FALSE; +static gboolean print_handler = FALSE; +static gchar *file_name = NULL; +static gchar *toplevel_name = NULL; +static gchar *css_file_name = NULL; +static gchar *screenshot_file_name = NULL; + +static GOptionEntry option_entries[] = +{ + {"filename", 'f', 0, G_OPTION_ARG_FILENAME, &file_name, N_("Name of the file to preview"), "FILENAME"}, + {"template", 0, 0, G_OPTION_ARG_NONE, &template, N_("Creates dummy widget class to load a template"), NULL}, + {"toplevel", 't', 0, G_OPTION_ARG_STRING, &toplevel_name, N_("Name of the toplevel to preview"), "TOPLEVELNAME"}, + {"screenshot", 0, 0, G_OPTION_ARG_FILENAME, &screenshot_file_name, N_("File name to save a screenshot"), NULL}, + {"css", 0, 0, G_OPTION_ARG_FILENAME, &css_file_name, N_("CSS file to use"), NULL}, + {"listen", 'l', 0, G_OPTION_ARG_NONE, &listen, N_("Listen standard input"), NULL}, + {"slideshow", 0, 0, G_OPTION_ARG_NONE, &slideshow, N_("make a slideshow of every toplevel widget by adding them in a GtkStack"), NULL}, + {"print-handler", 0, 0, G_OPTION_ARG_NONE, &print_handler, N_("Print handlers signature on invocation"), NULL}, + {"version", 'v', 0, G_OPTION_ARG_NONE, &version, N_("Display previewer version"), NULL}, + {NULL} +}; + +int +main (int argc, char **argv) +{ + GladePreviewerApp *app; + GOptionContext *context; + GError *error = NULL; + GObject *toplevel = NULL; + +#ifdef ENABLE_NLS + setlocale (LC_ALL, ""); + bindtextdomain (GETTEXT_PACKAGE, glade_app_get_locale_dir ()); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + textdomain (GETTEXT_PACKAGE); +#endif + + context = g_option_context_new (_("- previews a glade UI definition")); + g_option_context_add_main_entries (context, option_entries, GETTEXT_PACKAGE); + g_option_context_add_group (context, gtk_get_option_group (TRUE)); + + if (!g_option_context_parse (context, &argc, &argv, &error)) + { + g_printerr (_("%s\nRun '%s --help' to see a full list of available command line " + "options.\n"), error->message, argv[0]); + g_error_free (error); + g_option_context_free (context); + return 1; + } + + g_option_context_free (context); + + if (version) + { + g_print ("glade-previewer " VERSION "\n"); + return 0; + } + + if (!listen && !file_name) + { + g_printerr (_("Either --listen or --filename must be specified.\n")); + return 0; + } + + gtk_init (&argc, &argv); + glade_app_get (); + + app = glade_previewer_app_new (file_name, toplevel_name); + + app->is_template = template; + + if (print_handler) + glade_previewer_set_print_handlers (GLADE_PREVIEWER (app->preview), TRUE); + + if (css_file_name) + glade_previewer_set_css_file (app->preview, css_file_name); + + if (listen) + { +#ifdef WINDOWS + GIOChannel *input = g_io_channel_win32_new_fd (fileno (stdin)); +#else + GIOChannel *input = g_io_channel_unix_new (fileno (stdin)); +#endif + + g_io_add_watch (input, G_IO_IN | G_IO_HUP, on_data_incoming, app); + + gtk_main (); + } + else if (template) + { + gchar *contents = NULL; + gsize size; + + if (g_file_get_contents (file_name, &contents, &size, NULL)) + toplevel = get_toplevel_from_string (app, NULL, contents, size); + + g_free (contents); + } + else if (file_name) + { + GtkBuilder *builder = gtk_builder_new (); + GError *error = NULL; + + /* Use from_file() function gives builder a chance to know where to load resources from */ + if (!gtk_builder_add_from_file (builder, app->file_name, &error)) + { + g_printerr (_("Couldn't load builder definition: %s"), error->message); + g_error_free (error); + return 1; + } + + if (slideshow) + { + GSList *objects = gtk_builder_get_objects (builder); + + glade_previewer_set_slideshow_widgets (app->preview, objects); + glade_previewer_present (app->preview); + + if (screenshot_file_name) + glade_previewer_slideshow_save (app->preview, screenshot_file_name); + else + gtk_main (); + + g_slist_free (objects); + } + else + { + toplevel = get_toplevel (builder, toplevel_name); + + gtk_builder_connect_signals_full (builder, + glade_previewer_connect_function, + app->preview); + } + + g_object_unref (builder); + } + + if (toplevel) + { + glade_previewer_set_widget (app->preview, GTK_WIDGET (toplevel)); + g_object_unref (toplevel); + glade_previewer_present (app->preview); + + if (screenshot_file_name) + glade_previewer_screenshot (app->preview, TRUE, screenshot_file_name); + else + gtk_main (); + } + + /* free unused resources */ + g_free (file_name); + g_free (toplevel_name); + g_free (css_file_name); + g_free (screenshot_file_name); + glade_previewer_free (app); + + return 0; +} diff --git a/gladeui/glade-previewer.c b/gladeui/glade-previewer.c new file mode 100644 index 0000000..0fa64ea --- /dev/null +++ b/gladeui/glade-previewer.c @@ -0,0 +1,869 @@ +/* + * glade-previewer.c + * + * Copyright (C) 2013-2016 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#include + +#include "glade-previewer.h" +#include +#include +#include +#include +#include + +struct _GladePreviewerPrivate +{ + GtkWidget *widget; /* Preview widget */ + GList *objects; /* SlideShow objects */ + GtkWidget *dialog; /* Dialog to show messages */ + GtkWidget *textview; + + GtkCssProvider *css_provider; + GFileMonitor *css_monitor; + gchar *css_file; + gchar *extension; + + gboolean print_handlers; +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GladePreviewer, glade_previewer, G_TYPE_OBJECT) + +static void +glade_previewer_init (GladePreviewer *preview) +{ + GladePreviewerPrivate *priv = glade_previewer_get_instance_private (preview); + + preview->priv = priv; +} + +static void +glade_previewer_dispose (GObject *object) +{ + GladePreviewerPrivate *priv = GLADE_PREVIEWER (object)->priv; + + g_list_free (priv->objects); + + priv->objects = NULL; + priv->dialog = NULL; + g_clear_object (&priv->css_provider); + g_clear_object (&priv->css_monitor); + + G_OBJECT_CLASS (glade_previewer_parent_class)->dispose (object); +} + +static void +glade_previewer_finalize (GObject *object) +{ + GladePreviewerPrivate *priv = GLADE_PREVIEWER (object)->priv; + + g_free (priv->css_file); + g_free (priv->extension); + + G_OBJECT_CLASS (glade_previewer_parent_class)->finalize (object); +} + +static gboolean +on_widget_key_press_event (GtkWidget *widget, + GdkEventKey *event, + GladePreviewer *preview) +{ + GladePreviewerPrivate *priv = preview->priv; + GList *node = NULL; + GtkStack *stack; + gchar *extension; + + if (priv->objects) + { + stack = GTK_STACK (gtk_bin_get_child (GTK_BIN (priv->widget))); + node = g_list_find (priv->objects, gtk_stack_get_visible_child (stack)); + } + + switch (event->keyval) + { + case GDK_KEY_Page_Up: + if (node && node->prev) + gtk_stack_set_visible_child (stack, node->prev->data); + return TRUE; + break; + case GDK_KEY_Page_Down: + if (node && node->next) + gtk_stack_set_visible_child (stack, node->next->data); + return TRUE; + break; + case GDK_KEY_F5: + extension = "svg"; + break; + case GDK_KEY_F6: + extension = "ps"; + break; + case GDK_KEY_F7: + extension = "pdf"; + break; + case GDK_KEY_F8: + extension = priv->extension ? priv->extension : "png"; + break; + case GDK_KEY_F11: + if (gdk_window_get_state (gtk_widget_get_window (widget)) & GDK_WINDOW_STATE_FULLSCREEN) + gtk_window_unfullscreen (GTK_WINDOW (widget)); + else + gtk_window_fullscreen (GTK_WINDOW (widget)); + + return TRUE; + break; + default: + return FALSE; + break; + } + + if (extension) + { + gchar *tmp_file = g_strdup_printf ("glade-screenshot-XXXXXX.%s", extension); + + g_mkstemp (tmp_file); + glade_previewer_screenshot (preview, FALSE, tmp_file); + g_free (tmp_file); + + return TRUE; + } + + return FALSE; +} + +static void +glade_previewer_class_init (GladePreviewerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = glade_previewer_dispose; + object_class->finalize = glade_previewer_finalize; +} + +GObject * +glade_previewer_new (void) +{ + return g_object_new (GLADE_TYPE_PREVIEWER, NULL); +} + +void +glade_previewer_present (GladePreviewer *preview) +{ + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + gtk_window_present (GTK_WINDOW (preview->priv->widget)); +} + +void +glade_previewer_set_widget (GladePreviewer *preview, GtkWidget *widget) +{ + GladePreviewerPrivate *priv; + GtkWidget *sw; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + g_return_if_fail (GTK_IS_WIDGET (widget)); + + priv = preview->priv; + + if (priv->widget) + gtk_widget_destroy (priv->widget); + + if (!gtk_widget_is_toplevel (widget)) + { + GtkWidget *window = gtk_window_new (GTK_WINDOW_TOPLEVEL); + + gtk_container_add (GTK_CONTAINER (window), widget); + + priv->widget = window; + } + else + { + priv->widget = widget; + } + + /* Create dialog to display messages */ + priv->dialog = gtk_window_new (GTK_WINDOW_TOPLEVEL); + gtk_window_set_default_size (GTK_WINDOW (priv->dialog), 640, 320); + gtk_window_set_title (GTK_WINDOW (priv->dialog), _("Glade Previewer log")); + gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), GTK_WINDOW (priv->widget)); + + priv->textview = gtk_text_view_new (); + gtk_widget_show (priv->textview); + + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_widget_show (sw); + + gtk_container_add (GTK_CONTAINER (sw), priv->textview); + gtk_container_add (GTK_CONTAINER (priv->dialog), sw); + + /* Hide dialog on delete event */ + g_signal_connect (priv->dialog, "delete-event", + G_CALLBACK (gtk_widget_hide), + NULL); + + /* Quit on delete event */ + g_signal_connect (priv->widget, "delete-event", + G_CALLBACK (gtk_main_quit), + NULL); + + /* Make sure we get press events */ + gtk_widget_add_events (priv->widget, GDK_KEY_PRESS_MASK); + + /* Handle key presses for screenshot feature */ + g_signal_connect_object (priv->widget, "key-press-event", + G_CALLBACK (on_widget_key_press_event), + preview, 0); +} + +void +glade_previewer_set_message (GladePreviewer *preview, + GtkMessageType type, + const gchar *message) +{ + GladePreviewerPrivate *priv; + GtkTextBuffer *buffer; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; + + if (!priv->textview) + return; + + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (priv->textview)); + + if (message) + { + GtkTextIter iter; + + /* TODO: use message type to color text */ + gtk_text_buffer_get_start_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, "\n", -1); + + gtk_text_buffer_get_start_iter (buffer, &iter); + gtk_text_buffer_insert (buffer, &iter, message, -1); + + gtk_window_present (GTK_WINDOW (priv->dialog)); + } +} + +static void +on_css_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GladePreviewer *preview) +{ + GladePreviewerPrivate *priv = preview->priv; + GError *error = NULL; + + gtk_css_provider_load_from_file (priv->css_provider, file, &error); + + if (error) + { + glade_previewer_set_message (preview, GTK_MESSAGE_WARNING, error->message); + g_error_free (error); + } + else + glade_previewer_set_message (preview, GTK_MESSAGE_OTHER, NULL); +} + +void +glade_previewer_set_css_file (GladePreviewer *preview, + const gchar *css_file) +{ + GladePreviewerPrivate *priv; + GError *error = NULL; + GFile *file; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; + + g_free (priv->css_file); + g_clear_object (&priv->css_monitor); + + priv->css_file = g_strdup (css_file); + + file = g_file_new_for_path (css_file); + + if (!priv->css_provider) + { + priv->css_provider = gtk_css_provider_new (); + g_object_ref_sink (priv->css_provider); + + /* Set provider for default screen once */ + gtk_style_context_add_provider_for_screen (gdk_screen_get_default (), + GTK_STYLE_PROVIDER (priv->css_provider), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + } + + priv->css_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, &error); + if (error) + { + g_warning ("Cant monitor CSS file %s: %s", css_file, error->message); + g_error_free (error); + } + else + { + g_object_ref_sink (priv->css_monitor); + g_signal_connect (priv->css_monitor, "changed", + G_CALLBACK (on_css_monitor_changed), preview); + } + + /* load CSS */ + gtk_css_provider_load_from_file (priv->css_provider, file, &error); + if (error) + { + glade_previewer_set_message (preview, GTK_MESSAGE_INFO, error->message); + g_message ("%s CSS parsing failed: %s", css_file, error->message); + g_error_free (error); + } + + g_object_unref (file); +} + +void +glade_previewer_set_screenshot_extension (GladePreviewer *preview, + const gchar *extension) +{ + GladePreviewerPrivate *priv; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; + + g_free (priv->extension); + priv->extension = g_strdup (extension); +} + +static gboolean +quit_when_idle (gpointer loop) +{ + g_main_loop_quit (loop); + + return G_SOURCE_REMOVE; +} + +static void +check_for_draw (GdkEvent *event, gpointer loop) +{ + if (event->type == GDK_EXPOSE) + { + g_idle_add (quit_when_idle, loop); + gdk_event_handler_set ((GdkEventFunc) gtk_main_do_event, NULL, NULL); + } + + gtk_main_do_event (event); +} + +/* Taken from Gtk sources gtk-reftest.c */ +static void +glade_previewer_wait_for_drawing (GdkWindow *window) +{ + GMainLoop *loop; + + loop = g_main_loop_new (NULL, FALSE); + /* We wait until the widget is drawn for the first time. + * We can not wait for a GtkWidget::draw event, because that might not + * happen if the window is fully obscured by windowed child widgets. + * Alternatively, we could wait for an expose event on widget's window. + * Both of these are rather hairy, not sure what's best. */ + gdk_event_handler_set (check_for_draw, loop, NULL); + g_main_loop_run (loop); + + /* give the WM/server some time to sync. They need it. + * Also, do use popups instead of toplevls in your tests + * whenever you can. */ + gdk_display_sync (gdk_window_get_display (window)); + g_timeout_add (500, quit_when_idle, loop); + g_main_loop_run (loop); +} + +static const gchar * +glade_previewer_get_extension (const gchar *filename) +{ + gchar *extension; + + g_return_val_if_fail (filename != NULL, NULL); + + extension = g_strrstr (filename,"."); + + if (extension) + extension++; + + if (!extension) + { + g_warning ("%s has no extension!", filename); + return NULL; + } + return extension; +} + +static void +glade_previewer_get_scale (GdkScreen *screen, gdouble *sx, gdouble *sy) +{ + if (sx) + *sx = 72.0 / (gdk_screen_get_width (screen) / (gdk_screen_get_width_mm (screen) * 0.03937008)); + + if (sy) + *sy = 72.0 / (gdk_screen_get_height (screen) / (gdk_screen_get_height_mm (screen) * 0.03937008)); +} + +static cairo_surface_t * +glade_previewer_surface_from_file (const gchar *filename, gdouble w, gdouble h) +{ + cairo_surface_t *surface; + const gchar *extension; + + extension = glade_previewer_get_extension (filename); + + if (extension == NULL) + return NULL; + + if (g_strcmp0 (extension, "svg") == 0) +#if CAIRO_HAS_SVG_SURFACE + surface = cairo_svg_surface_create (filename, w, h); +#else + g_warning ("PDF not supported by the cairo version used"); +#endif + else if (g_strcmp0 (extension, "ps") == 0) +#if CAIRO_HAS_PS_SURFACE + surface = cairo_ps_surface_create (filename, w, h); +#else + g_warning ("PS not supported by the cairo version used"); +#endif + else if (g_strcmp0 (extension, "pdf") == 0) +#if CAIRO_HAS_PDF_SURFACE + surface = cairo_pdf_surface_create (filename, w, h); +#else + g_warning ("PDF not supported by the cairo version used"); +#endif + else + return NULL; + + return surface; +} + +/** + * glade_previewer_screenshot: + * @preview: A GladePreviewer + * @wait: True if it should wait for widget to draw. + * @filename: a filename to save the image. + * + * Takes a screenshot of the current widget @window is showing and save it to @filename + * Supported extension are svg, ps, pdf and wahtever gdk-pixbuf supports + */ +void +glade_previewer_screenshot (GladePreviewer *preview, + gboolean wait, + const gchar *filename) +{ + GladePreviewerPrivate *priv; + cairo_surface_t *surface; + GdkWindow *gdkwindow; + GdkScreen *screen; + gdouble sx, sy; + gint w, h; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + g_return_if_fail (filename != NULL); + priv = preview->priv; + + if (!priv->widget) + return; + + gdkwindow = gtk_widget_get_window (priv->widget); + screen = gdk_window_get_screen (gdkwindow); + + if (wait) + glade_previewer_wait_for_drawing (gdkwindow); + + w = gtk_widget_get_allocated_width (priv->widget); + h = gtk_widget_get_allocated_height (priv->widget); + glade_previewer_get_scale (screen, &sx, &sy); + + surface = glade_previewer_surface_from_file (filename, w*sx, h*sy); + + if (surface) + { + cairo_t *cr = cairo_create (surface); + cairo_scale (cr, sx, sy); + gtk_widget_draw (priv->widget, cr); + cairo_destroy (cr); + cairo_surface_destroy(surface); + } + else + { + GdkPixbuf *pix = gdk_pixbuf_get_from_window (gdkwindow, 0, 0, w, h); + const gchar *ext = glade_previewer_get_extension (filename); + GError *error = NULL; + + if (!gdk_pixbuf_save (pix, filename, ext ? ext : "png", &error, NULL)) + { + g_warning ("Could not save screenshot to %s because %s", filename, error->message); + g_error_free (error); + } + + g_object_unref (pix); + } +} + +static gint +objects_cmp_func (gconstpointer a, gconstpointer b) +{ + const gchar *name_a, *name_b; + name_a = gtk_buildable_get_name (GTK_BUILDABLE (a)); + name_b = gtk_buildable_get_name (GTK_BUILDABLE (b)); + return g_strcmp0 (name_a, name_b); +} + +/** + * glade_previewer_set_slideshow_widgets: + * @preview: A GladePreviewer + * @objects: GSlist of GObject + * + * Add a list of objects to slideshow + */ +void +glade_previewer_set_slideshow_widgets (GladePreviewer *preview, + GSList *objects) +{ + GladePreviewerPrivate *priv; + GtkStack *stack; + GSList *l; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + priv = preview->priv; + + stack = GTK_STACK (gtk_stack_new ()); + gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_CROSSFADE); + + objects = g_slist_sort (g_slist_copy (objects), objects_cmp_func); + + for (l = objects; l; l = g_slist_next (l)) + { + GObject *obj = l->data; + + if (!GTK_IS_WIDGET (obj) || gtk_widget_get_parent (GTK_WIDGET (obj))) + continue; + + /* TODO: make sure we can add a toplevel inside a stack */ + if (GTK_IS_WINDOW (obj)) + continue; + + priv->objects = g_list_prepend (priv->objects, obj); + + gtk_stack_add_named (stack, GTK_WIDGET (obj), + gtk_buildable_get_name (GTK_BUILDABLE (obj))); + } + + priv->objects = g_list_reverse (priv->objects); + + glade_previewer_set_widget (preview, GTK_WIDGET (stack)); + gtk_widget_show (GTK_WIDGET (stack)); + + g_slist_free (objects); +} + +/** + * glade_previewer_slideshow_save: + * @preview: A GladePreviewer + * @filename: a filename to save the slideshow. + * + * Takes a screenshot of every widget GtkStack children and save it to @filename + * each in a different page + */ +void +glade_previewer_slideshow_save (GladePreviewer *preview, + const gchar *filename) +{ + GladePreviewerPrivate *priv; + cairo_surface_t *surface; + GdkWindow *gdkwindow; + GtkWidget *child; + GtkStack *stack; + gdouble sx, sy; + + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + g_return_if_fail (filename != NULL); + priv = preview->priv; + + g_return_if_fail (GTK_IS_BIN (priv->widget)); + + child = gtk_bin_get_child (GTK_BIN (priv->widget)); + g_return_if_fail (GTK_IS_STACK (child)); + stack = GTK_STACK (child); + + gtk_stack_set_transition_type (stack, GTK_STACK_TRANSITION_TYPE_NONE); + + gdkwindow = gtk_widget_get_window (priv->widget); + glade_previewer_wait_for_drawing (gdkwindow); + + glade_previewer_get_scale (gtk_widget_get_screen (GTK_WIDGET (priv->widget)), &sx, &sy); + surface = glade_previewer_surface_from_file (filename, + gtk_widget_get_allocated_width (GTK_WIDGET (stack))*sx, + gtk_widget_get_allocated_height (GTK_WIDGET (stack))*sy); + + if (surface) + { + GList *l, *children = gtk_container_get_children (GTK_CONTAINER (stack)); + cairo_t *cr= cairo_create (surface); + + cairo_scale (cr, sx, sy); + + for (l = children; l; l = g_list_next (l)) + { + GtkWidget *child = l->data; + gtk_stack_set_visible_child (stack, child); + glade_previewer_wait_for_drawing (gdkwindow); + gtk_widget_draw (child, cr); + cairo_show_page (cr); + } + + if (children) + gtk_stack_set_visible_child (stack, children->data); + + g_list_free (children); + cairo_destroy (cr); + cairo_surface_destroy(surface); + } + else + g_warning ("Could not save slideshow to %s", filename); +} + +/** + * glade_previewer_set_print_handlers: + * @preview: A GladePreviewer + * @print: whether to print handlers or not + * + * Set whether to print handlers when they are activated or not. + * It only works if you use glade_previewer_connect_function() as the + * connect function. + */ +void +glade_previewer_set_print_handlers (GladePreviewer *preview, + gboolean print) +{ + g_return_if_fail (GLADE_IS_PREVIEWER (preview)); + preview->priv->print_handlers = print; +} + +typedef struct +{ + gchar *handler_name; + GObject *connect_object; + GConnectFlags flags; +} HandlerData; + +typedef struct +{ + GladePreviewer *window; + gint n_invocations; + + GSignalQuery query; + GObject *object; + GList *handlers; +} SignalData; + +static void +handler_data_free (gpointer udata) +{ + HandlerData *hd = udata; + g_clear_object (&hd->connect_object); + g_free (hd->handler_name); + g_free (hd); +} + +static void +signal_data_free (gpointer udata, GClosure *closure) +{ + SignalData *data = udata; + + g_list_free_full (data->handlers, handler_data_free); + data->handlers = NULL; + + g_clear_object (&data->window); + g_clear_object (&data->object); + + g_free (data); +} + +static inline const gchar * +object_get_name (GObject *object) +{ + if (GTK_IS_BUILDABLE (object)) + return gtk_buildable_get_name (GTK_BUILDABLE (object)); + else + return g_object_get_data (object, "gtk-builder-name"); +} + +static void +glade_handler_append (GString *message, + GSignalQuery *query, + const gchar *object, + GList *handlers, + gboolean after) +{ + GList *l; + + for (l = handlers; l; l = g_list_next (l)) + { + HandlerData *hd = l->data; + gboolean handler_after = (hd->flags & G_CONNECT_AFTER); + gboolean swapped = (hd->flags & G_CONNECT_SWAPPED); + GObject *obj = hd->connect_object; + gint i; + + if ((after && !handler_after) || (!after && handler_after)) + continue; + + g_string_append_printf (message, "\n\t-> %s%s %s (%s%s%s", + g_type_name (query->return_type), + g_type_is_a (query->return_type, G_TYPE_OBJECT) ? " *" : "", + hd->handler_name, + (swapped) ? ((obj) ? G_OBJECT_TYPE_NAME (obj) : "") : g_type_name (query->itype), + (swapped) ? ((obj) ? " *" : "") : " *", + (swapped) ? ((obj) ? object_get_name (obj) : _("user_data")) : object); + + for (i = 1; i < query->n_params; i++) + g_string_append_printf (message, ", %s%s", + g_type_name (query->param_types[i]), + g_type_is_a (query->param_types[i], G_TYPE_OBJECT) ? " *" : ""); + + g_string_append_printf (message, ", %s%s%s); ", + (swapped) ? g_type_name (query->itype) : ((obj) ? G_OBJECT_TYPE_NAME (obj) : ""), + (swapped) ? " *" : ((obj) ? " *" : ""), + (swapped) ? object : ((obj) ? object_get_name (obj) : _("user_data"))); + + if (swapped && after) + /* translators: GConnectFlags values */ + g_string_append (message, _("Swapped | After")); + else if (swapped) + /* translators: GConnectFlags value */ + g_string_append (message, _("Swapped")); + else if (after) + /* translators: GConnectFlags value */ + g_string_append (message, _("After")); + } +} + +static inline void +glade_handler_method_append (GString *msg, GSignalQuery *q, const gchar *flags) +{ + g_string_append_printf (msg, "\n\t%sClass->%s(); %s", g_type_name (q->itype), + q->signal_name, flags); +} + +static void +on_handler_called (SignalData *data) +{ + GSignalQuery *query = &data->query; + GObject *object = data->object; + const gchar *object_name = object_get_name (object); + GString *message = g_string_new (""); + + data->n_invocations++; + + if (data->n_invocations == 1) + /* translators: this will be shown in glade previewer when a signal %s::%s is emitted one time */ + g_string_append_printf (message, _("%s::%s emitted one time"), + G_OBJECT_TYPE_NAME (object), query->signal_name); + else + /* translators: this will be shown in glade previewer when a signal %s::%s is emitted %d times */ + g_string_append_printf (message, _("%s::%s emitted %d times"), + G_OBJECT_TYPE_NAME (object), query->signal_name, + data->n_invocations); + + if (query->signal_flags & G_SIGNAL_RUN_FIRST) + glade_handler_method_append (message, query, _("Run First")); + + glade_handler_append (message, query, object_name, data->handlers, FALSE); + + if (query->signal_flags & G_SIGNAL_RUN_LAST) + glade_handler_method_append (message, query, _("Run Last")); + + glade_handler_append (message, query, object_name, data->handlers, TRUE); + + if (query->signal_flags & G_SIGNAL_RUN_CLEANUP) + glade_handler_method_append (message, query, _("Run Cleanup")); + + glade_previewer_set_message (data->window, GTK_MESSAGE_INFO, message->str); + + if (data->window->priv->print_handlers) + g_printf ("\n%s\n", message->str); + + g_string_free (message, TRUE); +} + +/** + * glade_previewer_connect_function: + * @builder: a #GtkBuilder + * @object: the #GObject triggering the signal + * @signal_name: the name of the signal + * @handler_name: the name of the c function handling the signal + * @connect_object: the user_data #GObject to connect + * @flags: #GConnectFlags used in the connection + * @window: a #GladePreviewer + * + * Function that collects every signal handler in @builder and shows them + * in @window info bar when the callback is activated + */ +void +glade_previewer_connect_function (GtkBuilder *builder, + GObject *object, + const gchar *signal_name, + const gchar *handler_name, + GObject *connect_object, + GConnectFlags flags, + gpointer window) +{ + SignalData *data; + HandlerData *hd; + guint signal_id; + gchar *key; + + g_return_if_fail (GLADE_IS_PREVIEWER (window)); + + if (!(signal_id = g_signal_lookup (signal_name, G_OBJECT_TYPE (object)))) + return; + + key = g_strconcat ("glade-signal-data-", signal_name, NULL); + data = g_object_get_data (object, key); + + if (!data) + { + data = g_new0 (SignalData, 1); + + data->window = g_object_ref (window); + g_signal_query (signal_id, &data->query); + data->object = g_object_ref (object); + + g_signal_connect_data (object, signal_name, + G_CALLBACK (on_handler_called), + data, signal_data_free, G_CONNECT_SWAPPED); + + g_object_set_data (object, key, data); + } + + hd = g_new0 (HandlerData, 1); + hd->handler_name = g_strdup (handler_name); + hd->connect_object = connect_object ? g_object_ref (connect_object) : NULL; + hd->flags = flags; + + data->handlers = g_list_append (data->handlers, hd); + + g_free (key); +} diff --git a/gladeui/glade-previewer.h b/gladeui/glade-previewer.h new file mode 100644 index 0000000..3284c19 --- /dev/null +++ b/gladeui/glade-previewer.h @@ -0,0 +1,97 @@ +/* + * glade-previewer.h + * + * Copyright (C) 2013-2016 Juan Pablo Ugarte + * + * Author: Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef _GLADE_PREVIEWER_H_ +#define _GLADE_PREVIEWER_H_ + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PREVIEWER (glade_previewer_get_type ()) +#define GLADE_PREVIEWER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PREVIEWER, GladePreviewer)) +#define GLADE_PREVIEWER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PREVIEWER, GladePreviewerClass)) +#define GLADE_IS_PREVIEWER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PREVIEWER)) +#define GLADE_IS_PREVIEWER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PREVIEWER)) +#define GLADE_PREVIEWER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PREVIEWER, GladePreviewerClass)) + +typedef struct _GladePreviewerClass GladePreviewerClass; +typedef struct _GladePreviewer GladePreviewer; +typedef struct _GladePreviewerPrivate GladePreviewerPrivate; + + +struct _GladePreviewerClass +{ + GObjectClass parent_class; +}; + +struct _GladePreviewer +{ + GObject parent_instance; + + GladePreviewerPrivate *priv; +}; + +GType glade_previewer_get_type (void) G_GNUC_CONST; + +GObject *glade_previewer_new (void); + +void glade_previewer_set_widget (GladePreviewer *preview, + GtkWidget *widget); + +void glade_previewer_present (GladePreviewer *preview); + +void glade_previewer_set_print_handlers (GladePreviewer *preview, + gboolean print); + +void glade_previewer_set_message (GladePreviewer *preview, + GtkMessageType type, + const gchar *message); + +void glade_previewer_set_css_file (GladePreviewer *preview, + const gchar *css_file); + +void glade_previewer_set_screenshot_extension (GladePreviewer *preview, + const gchar *extension); + +void glade_previewer_screenshot (GladePreviewer *preview, + gboolean wait, + const gchar *filename); + +void glade_previewer_set_slideshow_widgets (GladePreviewer *preview, + GSList *objects); + +void glade_previewer_slideshow_save (GladePreviewer *preview, + const gchar *filename); + +void glade_previewer_connect_function (GtkBuilder *builder, + GObject *object, + const gchar *signal_name, + const gchar *handler_name, + GObject *connect_object, + GConnectFlags flags, + gpointer window); + +G_END_DECLS + +#endif /* _GLADE_PREVIEWER_H_ */ diff --git a/gladeui/glade-previewer.rc.in b/gladeui/glade-previewer.rc.in new file mode 100644 index 0000000..0250b5b --- /dev/null +++ b/gladeui/glade-previewer.rc.in @@ -0,0 +1,29 @@ +#include + +GLADE_ICON ICON "@SOURCE_ROOT@/data/icons/glade.ico" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION @GLADE_MAJOR_VERSION@,@GLADE_MINOR_VERSION@,@GLADE_MICRO_VERSION@,0 + PRODUCTVERSION @GLADE_MAJOR_VERSION@,@GLADE_MINOR_VERSION@,@GLADE_MICRO_VERSION@,0 + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904B0" + BEGIN + VALUE "CompanyName", "The GNOME Foundation" + VALUE "FileDescription", "Glade Interface Designer @GLADE_MAJOR_VERSION@.@GLADE_MINOR_VERSION@.@GLADE_MICRO_VERSION@" + VALUE "FileVersion", "@GLADE_MAJOR_VERSION@.@GLADE_MINOR_VERSION@.@GLADE_MICRO_VERSION@.0" + VALUE "LegalCopyright", "Copyright 2007 The GNOME Foundation" + VALUE "OriginalFilename", "glade-previewer.exe" + VALUE "ProductName", "Glade Interface Designer" + VALUE "ProductVersion", "@GLADE_MAJOR_VERSION@.@GLADE_MINOR_VERSION@.@GLADE_MICRO_VERSION@.0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END + END + diff --git a/gladeui/glade-private.h b/gladeui/glade-private.h new file mode 100644 index 0000000..a5c744e --- /dev/null +++ b/gladeui/glade-private.h @@ -0,0 +1,124 @@ +/* + * glade-private.h: miscellaneous private API + * + * This is a placeholder for private API, eventually it should be replaced by + * proper public API or moved to its own private file. + * + * Copyright (C) 2013 Juan Pablo Ugarte + * + * Authors: + * Juan Pablo Ugarte + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#ifndef __GLADE_PRIVATE_H__ +#define __GLADE_PRIVATE_H__ + +#include "glade-widget.h" +#include "glade-project-properties.h" +#include "glade-property-def.h" + +G_BEGIN_DECLS + +#define GLADE_WIDGET_ADAPTOR_INSTANTIABLE_PREFIX_LEN 17 + +/* glade-widget.c */ + +GList *_glade_widget_peek_prop_refs (GladeWidget *widget); + +/* glade-catalog.c */ + +GladeCatalog *_glade_catalog_get_catalog (const gchar *name); +GList *_glade_catalog_tsort (GList *catalogs); + +/* glade-project.c */ + +void +_glade_project_emit_add_signal_handler (GladeWidget *widget, + const GladeSignal *signal); +void +_glade_project_emit_remove_signal_handler (GladeWidget *widget, + const GladeSignal *signal); +void +_glade_project_emit_change_signal_handler (GladeWidget *widget, + const GladeSignal *old_signal, + const GladeSignal *new_signal); +void +_glade_project_emit_activate_signal_handler (GladeWidget *widget, + const GladeSignal *signal); + +/* glade-project-properties.c */ +void +_glade_project_properties_set_warnings (GladeProjectProperties *props, + const gchar *warnings); + +void +_glade_project_properties_set_license_data (GladeProjectProperties *props, + const gchar *license, + const gchar *name, + const gchar *description, + const gchar *copyright, + const gchar *authors); +void +_glade_project_properties_get_license_data (GladeProjectProperties *props, + gchar **license, + gchar **name, + gchar **description, + gchar **copyright, + gchar **authors); + +/* glade-property-def.c */ +void +_glade_property_def_reset_version (GladePropertyDef *property_def); + +/* glade-utils.c */ + +gchar *_glade_util_compose_get_type_func (const gchar *name); + +void _glade_util_dialog_set_hig (GtkDialog *dialog); + +gchar *_glade_util_strreplace (gchar *str, + gboolean free_str, + const gchar *key, + const gchar *replacement); + +gchar *_glade_util_file_get_relative_path (GFile *target, + GFile *source); + +/* glade-xml-utils.c */ + +/* GladeXml Error handling */ +void _glade_xml_error_reset_last (void); +gchar *_glade_xml_error_get_last_message (void); + +/* glade-template.c */ +gchar *_glade_template_load (const gchar *filename, + gchar **type, + gchar **parent); + +gboolean _glade_template_parse (const gchar *tmpl, + gchar **type, + gchar **parent); + +GType _glade_template_generate_type (const gchar *type, + const gchar *parent); + +const gchar *_glade_template_lookup (const gchar *type); + +G_END_DECLS + +#endif /* __GLADE_PRIVATE_H__ */ diff --git a/gladeui/glade-project-properties.c b/gladeui/glade-project-properties.c new file mode 100644 index 0000000..4c0cb7b --- /dev/null +++ b/gladeui/glade-project-properties.c @@ -0,0 +1,1342 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * 2020 Juan Pablo Ugarte. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + * Juan Pablo Ugarte + */ + +#include +#include + +#include "glade-project-properties.h" +#include "glade-project.h" +#include "glade-command.h" +#include "glade-app.h" +#include "glade-utils.h" +#include "glade-private.h" + +/* GObjectClass */ +static void glade_project_properties_dispose (GObject *object); +static void glade_project_properties_finalize (GObject *object); +static void glade_project_properties_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); + +/* UI Callbacks */ +static void on_template_combo_box_changed (GtkComboBox *combo, + GladeProjectProperties *properties); +static void on_template_checkbutton_toggled (GtkToggleButton *togglebutton, + GladeProjectProperties *properties); +static void resource_default_toggled (GtkWidget *widget, + GladeProjectProperties *properties); +static void resource_relative_toggled (GtkWidget *widget, + GladeProjectProperties *properties); +static void resource_fullpath_toggled (GtkWidget *widget, + GladeProjectProperties *properties); +static void on_relative_path_entry_insert_text (GtkEditable *editable, + gchar *new_text, + gint new_text_length, + gint *position, + GladeProjectProperties *properties); +static void on_relative_path_entry_changed (GtkEntry *entry, + GladeProjectProperties *properties); +static void resource_full_path_set (GtkFileChooserButton *button, + GladeProjectProperties *properties); +static void verify_clicked (GtkWidget *button, + GladeProjectProperties *properties); +static void on_domain_entry_changed (GtkWidget *entry, + GladeProjectProperties *properties); +static void target_combobox_changed (GtkWidget *widget, + GladeProjectProperties *properties); +static void on_glade_project_properties_hide (GtkWidget *widget, + GladeProjectProperties *properties); +static void on_css_filechooser_file_set (GtkFileChooserButton *widget, + GladeProjectProperties *properties); +static void on_css_checkbutton_toggled (GtkWidget *widget, + GladeProjectProperties *properties); +static void on_license_comboboxtext_changed (GtkComboBox *widget, + GladeProjectProperties *properties); + +static void on_license_data_changed (GladeProjectProperties *properties); + +/* Project callbacks */ +static void project_path_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties); +static void project_resource_path_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties); +static void project_template_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties); +static void project_domain_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties); +static void project_targets_changed (GladeProject *project, + GladeProjectProperties *properties); +static void project_license_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties); +static void project_css_provider_path_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties); + +/* Toplevels model */ +enum +{ + COLUMN_ICON_NAME, + COLUMN_NAME, + COLUMN_ID, + COLUMN_WIDGET +}; + +typedef struct +{ + GladeProject *project; + GtkListStore *toplevels; + + /* Properties */ + GtkWidget *project_wide_radio; + GtkWidget *toplevel_contextual_radio; + GtkWidget *toolkit_grid; + + GtkWidget *resource_default_radio; + GtkWidget *resource_relative_radio; + GtkWidget *resource_fullpath_radio; + GtkWidget *relative_path_entry; + GtkWidget *full_path_button; + GtkWidget *domain_entry; + GtkWidget *template_combobox; + GtkWidget *template_checkbutton; + + GtkWidget *css_filechooser; + GtkWidget *css_checkbutton; + + GHashTable *target_combos; + + /* License */ + GtkComboBox *license_comboboxtext; + GtkTextView *license_textview; + GtkEntryBuffer *name_entrybuffer; + GtkEntryBuffer *description_entrybuffer; + GtkTextBuffer *authors_textbuffer; + GtkTextBuffer *copyright_textbuffer; + GtkTextBuffer *license_textbuffer; + GtkTextBuffer *warnings_textbuffer; + + gboolean ignore_ui_cb; +} GladeProjectPropertiesPrivate; + + +struct _GladeProjectProperties +{ + GtkDialog parent; + + GladeProjectPropertiesPrivate *priv; /* Unused */ +}; + +enum +{ + PROP_0, + PROP_PROJECT, +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeProjectProperties, glade_project_properties, GTK_TYPE_DIALOG) +#define GLADE_PROJECT_PROPERTIES_PRIVATE(o) ((GladeProjectPropertiesPrivate *) glade_project_properties_get_instance_private((GladeProjectProperties*)o)) + +/******************************************************** + * Class/Instance Init * + ********************************************************/ +static void +glade_project_properties_init (GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + priv->target_combos = g_hash_table_new_full (g_str_hash, g_str_equal, + g_free, NULL); + + gtk_widget_init_template (GTK_WIDGET (properties)); +} + +static void +glade_project_properties_class_init (GladeProjectPropertiesClass *klass) +{ + GObjectClass *gobject_class; + GtkWidgetClass *widget_class; + + gobject_class = G_OBJECT_CLASS (klass); + widget_class = GTK_WIDGET_CLASS (klass); + + gobject_class->dispose = glade_project_properties_dispose; + gobject_class->finalize = glade_project_properties_finalize; + gobject_class->set_property = glade_project_properties_set_property; + + g_object_class_install_property + (gobject_class, PROP_PROJECT, + g_param_spec_object ("project", _("Project"), + _("The project this properties dialog was created for"), + GLADE_TYPE_PROJECT, + G_PARAM_WRITABLE)); + + /* Setup the template GtkBuilder xml for this class + */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-project-properties.ui"); + + /* Define the relationship of the private entry and the entry defined in the xml + */ + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, toplevels); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, resource_default_radio); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, resource_relative_radio); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, resource_fullpath_radio); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, relative_path_entry); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, full_path_button); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, domain_entry); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, template_checkbutton); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, template_combobox); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, toolkit_grid); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, css_filechooser); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, css_checkbutton); + + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, license_comboboxtext); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, license_textview); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, name_entrybuffer); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, description_entrybuffer); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, authors_textbuffer); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, copyright_textbuffer); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, license_textbuffer); + gtk_widget_class_bind_template_child_private (widget_class, GladeProjectProperties, warnings_textbuffer); + + + /* Declare the callback ports that this widget class exposes, to bind with + * connections defined in the GtkBuilder xml + */ + gtk_widget_class_bind_template_callback (widget_class, on_template_combo_box_changed); + gtk_widget_class_bind_template_callback (widget_class, on_template_checkbutton_toggled); + gtk_widget_class_bind_template_callback (widget_class, resource_default_toggled); + gtk_widget_class_bind_template_callback (widget_class, resource_relative_toggled); + gtk_widget_class_bind_template_callback (widget_class, resource_fullpath_toggled); + gtk_widget_class_bind_template_callback (widget_class, resource_full_path_set); + gtk_widget_class_bind_template_callback (widget_class, verify_clicked); + gtk_widget_class_bind_template_callback (widget_class, on_domain_entry_changed); + gtk_widget_class_bind_template_callback (widget_class, on_relative_path_entry_insert_text); + gtk_widget_class_bind_template_callback (widget_class, on_relative_path_entry_changed); + gtk_widget_class_bind_template_callback (widget_class, on_glade_project_properties_hide); + gtk_widget_class_bind_template_callback (widget_class, on_css_filechooser_file_set); + gtk_widget_class_bind_template_callback (widget_class, on_css_checkbutton_toggled); + gtk_widget_class_bind_template_callback (widget_class, on_license_comboboxtext_changed); + gtk_widget_class_bind_template_callback (widget_class, on_license_data_changed); +} + +/******************************************************** + * GObjectClass * + ********************************************************/ +static void +glade_project_properties_dispose (GObject *object) +{ + /* Unset project to disconnect callbacks */ + g_object_set (object, "project", NULL, NULL); + + G_OBJECT_CLASS (glade_project_properties_parent_class)->dispose (object); +} + +static void +glade_project_properties_finalize (GObject *object) +{ + GladeProjectProperties *properties = GLADE_PROJECT_PROPERTIES (object); + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + g_hash_table_destroy (priv->target_combos); + + G_OBJECT_CLASS (glade_project_properties_parent_class)->finalize (object); +} + +static void +combobox_populate_from_catalog (GladeProjectProperties *properties, + GtkWidget *combobox, + GladeCatalog *catalog) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GladeProject *project = priv->project; + gint minor, major, position; + GList *targets; + + glade_project_get_target_version (project, + glade_catalog_get_name (catalog), + &major, &minor); + + for (targets = glade_catalog_get_targets (catalog), position = 0; + targets; targets = targets->next, position++) + { + GladeTargetableVersion *version = targets->data; + g_autofree gchar *name = g_strdup_printf ("%d.%d", + version->major, + version->minor); + + gtk_combo_box_text_insert (GTK_COMBO_BOX_TEXT(combobox), position, name, name); + if (major == version->major && minor == version->minor) + gtk_combo_box_set_active (GTK_COMBO_BOX(combobox), position); + + g_signal_connect (G_OBJECT (combobox), "changed", + G_CALLBACK (target_combobox_changed), properties); + g_object_set_data (G_OBJECT (combobox), "catalog", + (gchar *) glade_catalog_get_name (catalog)); + } + + g_hash_table_insert (priv->target_combos, + g_strdup (glade_catalog_get_name (catalog)), + combobox); +} + +static void +target_version_box_fill (GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GtkWidget *grid = priv->toolkit_grid; + GtkWidget *label, *combobox; + static gint n_columns = 3; + gint i, j, left; + GList *list; + + + /* Add stuff to the toolkit grid */ + for (list = glade_app_get_catalogs (), i = 0, j = 0; list; + list = g_list_next (list)) + { + GladeCatalog *catalog = list->data; + + /* Skip if theres only one option */ + if (g_list_length (glade_catalog_get_targets (catalog)) <= 1) + continue; + + /* Special case to mark GTK+ in upper case */ + if (strcmp (glade_catalog_get_name (catalog), "gtk+") == 0) + label = gtk_label_new ("GTK"); + else + label = gtk_label_new (glade_catalog_get_name (catalog)); + + left = (i % n_columns) * 2; + gtk_widget_set_halign (label, GTK_ALIGN_START); + gtk_grid_attach (GTK_GRID (grid), label, left, j, 1, 1); + gtk_widget_show (label); + + combobox = gtk_combo_box_text_new (); + gtk_widget_set_margin_end (combobox, 8); + combobox_populate_from_catalog (properties, combobox, catalog); + gtk_grid_attach (GTK_GRID (grid), combobox, left + 1, j, 1, 1); + gtk_widget_show (combobox); + + i++; + if (i % n_columns == 0) + j++; + } +} + +static void +update_prefs_for_resource_path (GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + const gchar *resource_path; + + resource_path = glade_project_get_resource_path (priv->project); + + if (resource_path == NULL) + { + gtk_entry_set_text (GTK_ENTRY (priv->relative_path_entry), ""); + gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->full_path_button)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->resource_default_radio), TRUE); + gtk_widget_set_sensitive (priv->full_path_button, FALSE); + gtk_widget_set_sensitive (priv->relative_path_entry, FALSE); + } + else if (g_path_is_absolute (resource_path) && + g_file_test (resource_path, G_FILE_TEST_IS_DIR)) + { + gtk_entry_set_text (GTK_ENTRY (priv->relative_path_entry), ""); + gtk_file_chooser_select_filename (GTK_FILE_CHOOSER (priv->full_path_button), + resource_path); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->resource_fullpath_radio), TRUE); + gtk_widget_set_sensitive (priv->full_path_button, TRUE); + gtk_widget_set_sensitive (priv->relative_path_entry, FALSE); + } + else + { + if (g_strcmp0 (resource_path, gtk_entry_get_text (GTK_ENTRY (priv->relative_path_entry)))) + gtk_entry_set_text (GTK_ENTRY (priv->relative_path_entry), resource_path); + + gtk_file_chooser_unselect_all (GTK_FILE_CHOOSER (priv->full_path_button)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->resource_relative_radio), TRUE); + gtk_widget_set_sensitive (priv->relative_path_entry, TRUE); + gtk_widget_set_sensitive (priv->full_path_button, FALSE); + } +} + +static void +on_project_add_widget (GladeProject *project, + GladeWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE (properties); + GladeWidgetAdaptor *adaptor; + const gchar *name; + GtkTreeIter iter; + + if (glade_widget_get_parent (widget)) + return; + + adaptor = glade_widget_get_adaptor (widget); + name = glade_widget_get_name (widget); + + gtk_list_store_append (priv->toplevels, &iter); + gtk_list_store_set (priv->toplevels, &iter, + COLUMN_ICON_NAME, + glade_widget_adaptor_get_icon_name (adaptor), + COLUMN_NAME, + g_str_has_prefix (name, "__glade_unnamed") ? + glade_widget_adaptor_get_name (adaptor) : name, + COLUMN_ID, + name, + COLUMN_WIDGET, + widget, + -1); +} + +static gboolean +get_iter_by_widget (GtkTreeModel *model, GladeWidget *widget, GtkTreeIter *iter) +{ + gboolean valid = gtk_tree_model_get_iter_first (model, iter); + + while (valid) + { + GladeWidget *gwidget; + + gtk_tree_model_get (model, iter, COLUMN_WIDGET, &gwidget, -1); + g_object_unref (gwidget); + + if (widget == gwidget) + return TRUE; + + valid = gtk_tree_model_iter_next (model, iter); + } + + return FALSE; +} + +static void +on_project_remove_widget (GladeProject *project, + GladeWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE (properties); + GtkTreeIter iter; + + if (glade_widget_get_parent (widget)) + return; + + if (get_iter_by_widget (GTK_TREE_MODEL (priv->toplevels), widget, &iter)) + gtk_list_store_remove (priv->toplevels, &iter); +} + +static void +on_project_widget_name_change (GladeProject *project, + GladeWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE (properties); + GtkTreeIter iter; + + if (glade_widget_get_parent (widget)) + return; + + if (get_iter_by_widget (GTK_TREE_MODEL (priv->toplevels), widget, &iter)) + { + GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (widget); + const gchar *name = glade_widget_get_name (widget); + + gtk_list_store_set (priv->toplevels, &iter, + COLUMN_NAME, + g_str_has_prefix (name, "__glade_unnamed") ? + glade_widget_adaptor_get_name (adaptor) : name, + COLUMN_ID, + name, + -1); + } +} + +static void +glade_project_properties_set_project (GladeProjectProperties *properties, + GladeProject *project) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + +#define PROJECT_DISCONNECT(func) g_signal_handlers_disconnect_by_func (priv->project, G_CALLBACK (func), properties) + + if (priv->project) + { + PROJECT_DISCONNECT (project_path_changed); + PROJECT_DISCONNECT (project_resource_path_changed); + PROJECT_DISCONNECT (project_template_changed); + PROJECT_DISCONNECT (project_domain_changed); + PROJECT_DISCONNECT (project_css_provider_path_changed); + PROJECT_DISCONNECT (project_targets_changed); + PROJECT_DISCONNECT (project_license_changed); + PROJECT_DISCONNECT (on_project_add_widget); + PROJECT_DISCONNECT (on_project_remove_widget); + PROJECT_DISCONNECT (on_project_widget_name_change); + } + + /* No strong reference, we belong to the project */ + priv->project = project; + + if (!priv->project) + return; + + g_signal_connect (priv->project, "notify::path", + G_CALLBACK (project_path_changed), properties); + g_signal_connect (priv->project, "notify::resource-path", + G_CALLBACK (project_resource_path_changed), properties); + g_signal_connect (priv->project, "notify::template", + G_CALLBACK (project_template_changed), properties); + g_signal_connect (priv->project, "notify::translation-domain", + G_CALLBACK (project_domain_changed), properties); + g_signal_connect (priv->project, "notify::css-provider-path", + G_CALLBACK (project_css_provider_path_changed), properties); + g_signal_connect (priv->project, "targets-changed", + G_CALLBACK (project_targets_changed), properties); + g_signal_connect (priv->project, "notify::license", + G_CALLBACK (project_license_changed), properties); + g_signal_connect (priv->project, "add-widget", + G_CALLBACK (on_project_add_widget), properties); + g_signal_connect (priv->project, "remove-widget", + G_CALLBACK (on_project_remove_widget), properties); + g_signal_connect (priv->project, "widget-name-changed", + G_CALLBACK (on_project_widget_name_change), properties); + + target_version_box_fill (properties); + update_prefs_for_resource_path (properties); + + project_path_changed (project, NULL, properties); + project_template_changed (project, NULL, properties); + project_domain_changed (project, NULL, properties); + project_css_provider_path_changed (project, NULL, properties); +} + +static void +glade_project_properties_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_PROJECT: + glade_project_properties_set_project (GLADE_PROJECT_PROPERTIES (object), + g_value_get_object (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/******************************************************** + * Callbacks * + ********************************************************/ +static void +target_combobox_changed (GtkWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + g_autofree gchar *version = NULL; + gchar *catalog; + gint major, minor; + + if (priv->ignore_ui_cb) + return; + + version = gtk_combo_box_text_get_active_text (GTK_COMBO_BOX_TEXT (widget)); + catalog = g_object_get_data (G_OBJECT (widget), "catalog"); + + if (sscanf (version, "%d.%d", &major, &minor) == 2) + glade_command_set_project_target (priv->project, catalog, major, minor); +} + +static void +resource_default_toggled (GtkWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + if (priv->ignore_ui_cb || + !gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + return; + + glade_command_set_project_resource_path (priv->project, NULL); +} + +static void +resource_relative_toggled (GtkWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (widget); + + if (priv->ignore_ui_cb || !gtk_toggle_button_get_active (toggle)) + return; + + glade_command_set_project_resource_path (priv->project, NULL); + gtk_toggle_button_set_active (toggle, TRUE); + gtk_widget_set_sensitive (priv->relative_path_entry, TRUE); + gtk_widget_set_sensitive (priv->full_path_button, FALSE); +} + +static void +resource_fullpath_toggled (GtkWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GtkToggleButton *toggle = GTK_TOGGLE_BUTTON (widget); + + if (priv->ignore_ui_cb || !gtk_toggle_button_get_active (toggle)) + return; + + glade_command_set_project_resource_path (priv->project, NULL); + gtk_toggle_button_set_active (toggle, TRUE); + gtk_widget_set_sensitive (priv->relative_path_entry, FALSE); + gtk_widget_set_sensitive (priv->full_path_button, TRUE); +} + +static void +on_relative_path_entry_insert_text (GtkEditable *editable, + gchar *new_text, + gint new_text_length, + gint *position, + GladeProjectProperties *properties) +{ + GString *fullpath = g_string_new (gtk_entry_get_text (GTK_ENTRY(editable))); + + g_string_insert (fullpath, *position, new_text); + + if (g_path_is_absolute (fullpath->str)) + g_signal_stop_emission_by_name (editable, "insert-text"); + + g_string_free (fullpath, TRUE); +} + +static void +on_relative_path_entry_changed (GtkEntry *entry, GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + if (priv->ignore_ui_cb) + return; + + glade_command_set_project_resource_path (priv->project, gtk_entry_get_text (entry)); +} + +static void +resource_full_path_set (GtkFileChooserButton *button, GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + gchar *text; + + if (priv->ignore_ui_cb) + return; + + text = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (button)); + glade_command_set_project_resource_path (priv->project, text); + g_free (text); +} + +static void +on_template_combo_box_changed (GtkComboBox *combo, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GtkTreeIter iter; + + if (priv->ignore_ui_cb) + return; + + if (gtk_combo_box_get_active_iter (combo, &iter)) + { + GladeWidget *gwidget; + + gtk_tree_model_get (gtk_combo_box_get_model (combo), &iter, + COLUMN_WIDGET, &gwidget, + -1); + g_object_unref (gwidget); + + glade_command_set_project_template (priv->project, gwidget); + } +} + +static void +on_template_checkbutton_toggled (GtkToggleButton *togglebutton, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + if (priv->ignore_ui_cb) + return; + + if (gtk_toggle_button_get_active (togglebutton)) + { + gboolean composite = FALSE; + GList *l; + + for (l = glade_project_toplevels (priv->project); l; l = l->next) + { + GObject *object = l->data; + GladeWidget *gwidget; + + gwidget = glade_widget_get_from_gobject (object); + + if (GTK_IS_WIDGET (object)) + { + glade_command_set_project_template (priv->project, gwidget); + composite = TRUE; + break; + } + } + + if (!composite) + gtk_toggle_button_set_active (togglebutton, FALSE); + } + else + glade_command_set_project_template (priv->project, NULL); +} + +static void +verify_clicked (GtkWidget *button, GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + if (glade_project_verify (priv->project, FALSE, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED)) + { + g_autofree gchar *name = NULL, *msg = NULL; + + name = glade_project_get_name (priv->project); + msg = g_strdup_printf (_("Project %s has no deprecated widgets " + "or version mismatches."), name); + + gtk_text_buffer_set_text (priv->warnings_textbuffer, msg, -1); + } +} + +static void +on_domain_entry_changed (GtkWidget *entry, GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + if (priv->ignore_ui_cb) + return; + + glade_command_set_project_domain (priv->project, gtk_entry_get_text (GTK_ENTRY (entry))); +} + +#define GNU_GPLv2_TEXT \ + "$(name) - $(description)\n" \ + "Copyright (C) $(copyright)\n" \ + "\n" \ + "This program is free software; you can redistribute it and/or\n" \ + "modify it under the terms of the GNU General Public License\n" \ + "as published by the Free Software Foundation; either version 2\n" \ + "of the License, or (at your option) any later version.\n" \ + "\n" \ + "This program is distributed in the hope that it will be useful,\n" \ + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \ + "GNU General Public License for more details.\n" \ + "\n" \ + "You should have received a copy of the GNU General Public License\n" \ + "along with this program; if not, write to the Free Software\n" \ + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.\n" + +#define GNU_LGPLv2_TEXT \ + "$(name) - $(description)\n" \ + "Copyright (C) $(copyright)\n" \ + "\n" \ + "This library is free software; you can redistribute it and/or\n" \ + "modify it under the terms of the GNU Lesser General Public\n" \ + "License as published by the Free Software Foundation; either\n" \ + "version 2.1 of the License, or (at your option) any later version.\n" \ + "\n" \ + "This library is distributed in the hope that it will be useful,\n" \ + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU\n" \ + "Lesser General Public License for more details.\n" \ + "\n" \ + "You should have received a copy of the GNU Lesser General Public\n" \ + "License along with this library; if not, write to the Free Software\n" \ + "Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA\n" + +#define GNU_GPLv3_TEXT \ + "Copyright (C) $(copyright)\n" \ + "\n" \ + "This file is part of $(name).\n" \ + "\n" \ + "$(name) is free software: you can redistribute it and/or modify\n" \ + "it under the terms of the GNU General Public License as published by\n" \ + "the Free Software Foundation, either version 3 of the License, or\n" \ + "(at your option) any later version.\n" \ + "\n" \ + "$(name) is distributed in the hope that it will be useful,\n" \ + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \ + "GNU General Public License for more details.\n" \ + "\n" \ + "You should have received a copy of the GNU General Public License\n" \ + "along with $(name). If not, see .\n" + +#define GNU_LGPLv3_TEXT \ + "Copyright (C) $(copyright)\n" \ + "\n" \ + "This file is part of $(name).\n" \ + "\n" \ + "$(name) is free software: you can redistribute it and/or modify\n" \ + "it under the terms of the GNU Lesser General Public License as published by\n" \ + "the Free Software Foundation, either version 3 of the License, or\n" \ + "(at your option) any later version.\n" \ + "\n" \ + "$(name) is distributed in the hope that it will be useful,\n" \ + "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" \ + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" \ + "GNU Lesser General Public License for more details.\n" \ + "\n" \ + "You should have received a copy of the GNU Lesser General Public License\n" \ + "along with $(name). If not, see .\n" + +#define BSD3c_TEXT \ + "Copyright (c) $(copyright)\n" \ + "All rights reserved.\n" \ + "\n" \ + "Redistribution and use in source and binary forms, with or without\n" \ + "modification, are permitted provided that the following conditions are met:\n" \ + " * Redistributions of source code must retain the above copyright\n" \ + " notice, this list of conditions and the following disclaimer.\n" \ + " * Redistributions in binary form must reproduce the above copyright\n" \ + " notice, this list of conditions and the following disclaimer in the\n" \ + " documentation and/or other materials provided with the distribution.\n" \ + " * Neither the name of the nor the\n" \ + " names of its contributors may be used to endorse or promote products\n" \ + " derived from this software without specific prior written permission.\n" \ + "\n" \ + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n" \ + "ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n" \ + "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n" \ + "DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY\n" \ + "DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n" \ + "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n" \ + "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n" \ + "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" \ + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n" \ + "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + +#define BSD2c_TEXT \ + "Copyright (c) $(copyright)\n" \ + "All rights reserved.\n" \ + "\n" \ + "Redistribution and use in source and binary forms, with or without\n" \ + "modification, are permitted provided that the following conditions are met:\n" \ + "\n" \ + "1. Redistributions of source code must retain the above copyright notice, this\n" \ + " list of conditions and the following disclaimer. \n" \ + "2. Redistributions in binary form must reproduce the above copyright notice,\n" \ + " this list of conditions and the following disclaimer in the documentation\n" \ + " and/or other materials provided with the distribution. \n" \ + "\n" \ + "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\n" \ + "ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\n" \ + "WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\n" \ + "DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\n" \ + "ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n" \ + "(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n" \ + "LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\n" \ + "ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n" \ + "(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n" \ + "SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n" + +#define APACHE2_TEXT \ + "Copyright $(copyright)\n" \ + "\n" \ + "Licensed under the Apache License, Version 2.0 (the \"License\"); \n" \ + "you may not use this file except in compliance with the License. \n" \ + "You may obtain a copy of the License at \n" \ + "\n" \ + " http://www.apache.org/licenses/LICENSE-2.0 \n" \ + "\n" \ + "Unless required by applicable law or agreed to in writing, software \n" \ + "distributed under the License is distributed on an \"AS IS\" BASIS, \n" \ + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. \n" \ + "See the License for the specific language governing permissions and \n" \ + "limitations under the License. \n" + +#define GNU_ALL_PERMISSIVE_TEXT \ + "Copyright (C) $(copyright)\n" \ + "\n" \ + "Copying and distribution of this file, with or without modification,\n" \ + "are permitted in any medium without royalty provided the copyright\n" \ + "notice and this notice are preserved. This file is offered as-is,\n" \ + "without any warranty.\n" + +#define MIT_TEXT \ + "The MIT License (MIT)\n" \ + "\n" \ + "Copyright (c) $(copyright)\n" \ + "\n" \ + "Permission is hereby granted, free of charge, to any person obtaining a copy\n" \ + "of this software and associated documentation files (the \"Software\"), to deal\n" \ + "in the Software without restriction, including without limitation the rights\n" \ + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n" \ + "copies of the Software, and to permit persons to whom the Software is\n" \ + "furnished to do so, subject to the following conditions:\n" \ + "\n" \ + "The above copyright notice and this permission notice shall be included in\n" \ + "all copies or substantial portions of the Software.\n" \ + "\n" \ + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n" \ + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n" \ + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n" \ + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n" \ + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n" \ + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\n" \ + "THE SOFTWARE.\n" + +static gchar * +gpp_get_license_from_id (const gchar *id) +{ + if (!g_strcmp0 (id, "gplv2")) + return GNU_GPLv2_TEXT; + else if (!g_strcmp0 (id, "gplv3")) + return GNU_GPLv3_TEXT; + else if (!g_strcmp0 (id, "lgplv2")) + return GNU_LGPLv2_TEXT; + else if (!g_strcmp0 (id, "lgplv3")) + return GNU_LGPLv3_TEXT; + else if (!g_strcmp0 (id, "bsd2c")) + return BSD2c_TEXT; + else if (!g_strcmp0 (id, "bsd3c")) + return BSD3c_TEXT; + else if (!g_strcmp0 (id, "apache2")) + return APACHE2_TEXT; + else if (!g_strcmp0 (id, "mit")) + return MIT_TEXT; + else if (!g_strcmp0 (id, "all_permissive")) + return GNU_ALL_PERMISSIVE_TEXT; + else + return NULL; +} + +static gint +string_count_new_lines (const gchar *str) +{ + gint c = 0; + + while (*str) + { + if (*str == '\n') + c++; + str = g_utf8_next_char (str); + } + return c; +} + +static void +gpp_update_license (GladeProjectProperties *properties, gchar *license) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + const gchar *name, *description; + gchar *copyright, *authors; + + if (!license) + return; + + /* get data */ + name = gtk_entry_buffer_get_text (priv->name_entrybuffer); + description = gtk_entry_buffer_get_text (priv->description_entrybuffer); + + g_object_get (priv->copyright_textbuffer, "text", ©right, NULL); + g_object_get (priv->authors_textbuffer, "text", &authors, NULL); + + /* Now we can replace strings in the license template */ + license = _glade_util_strreplace (license, FALSE, "$(name)", name); + license = _glade_util_strreplace (license, TRUE, "$(description)", description); + license = _glade_util_strreplace (license, TRUE, "$(copyright)", copyright); + + if (authors && *authors) + { + gchar *tmp = license; + + if (string_count_new_lines (authors)) + license = g_strconcat (license, "\n", "Authors:", "\n", authors, NULL); + else + license = g_strconcat (license, "\n", "Author:", " ", authors, NULL); + + g_free (tmp); + } + + gtk_text_buffer_set_text (priv->license_textbuffer, license, -1); + + g_free (license); + g_free (copyright); + g_free (authors); +} + +static void +on_license_data_changed (GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + const gchar *id = gtk_combo_box_get_active_id (priv->license_comboboxtext); + gchar *license; + + if ((license = gpp_get_license_from_id (id))) + gpp_update_license (properties, license); +} + +static void +on_license_comboboxtext_changed (GtkComboBox *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + gchar *license; + + if ((license = gpp_get_license_from_id (gtk_combo_box_get_active_id (widget)))) + { + gpp_update_license (properties, license); + gtk_text_view_set_editable (priv->license_textview, FALSE); + } + else + { + /* Other license */ + gtk_text_buffer_set_text (priv->license_textbuffer, "", -1); + gtk_text_view_set_editable (priv->license_textview, TRUE); + gtk_widget_grab_focus (GTK_WIDGET (priv->license_textview)); + } +} + +static void +on_glade_project_properties_hide (GtkWidget *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GtkTextIter start, end; + gchar *license; + + if (!priv->project) + return; + + gtk_text_buffer_get_bounds (priv->license_textbuffer, &start, &end); + license = gtk_text_buffer_get_text (priv->license_textbuffer, &start, &end, FALSE); + g_strstrip (license); + + glade_command_set_project_license (priv->project, (license[0] != '\0') ? license : NULL); + + g_free (license); +} + +static void +on_css_checkbutton_toggled (GtkWidget *widget, GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + if (priv->ignore_ui_cb) + return; + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (widget))) + { + gtk_widget_set_sensitive (priv->css_filechooser, TRUE); + } + else + { + gtk_widget_set_sensitive (priv->css_filechooser, FALSE); + glade_project_set_css_provider_path (priv->project, NULL); + } +} + +static void +on_css_filechooser_file_set (GtkFileChooserButton *widget, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + const gchar *path; + + if (priv->ignore_ui_cb) + return; + + path = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (widget)); + + glade_project_set_css_provider_path (priv->project, path); +} + +/****************************************************** + * Project Callbacks * + ******************************************************/ +static void +project_targets_changed (GladeProject *project, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GList *list; + priv->ignore_ui_cb = TRUE; + + /* For each catalog */ + for (list = glade_app_get_catalogs (); list; list = g_list_next (list)) + { + GladeCatalog *catalog = list->data; + GtkComboBox *combobox; + gint minor, major; + const gchar *name; + + /* Skip if theres only one option */ + if (g_list_length (glade_catalog_get_targets (catalog)) <= 1) + continue; + + /* Fetch the version for this project */ + name = glade_catalog_get_name (catalog); + glade_project_get_target_version (priv->project, name, &major, &minor); + + /* Fetch the radios for this catalog */ + if (priv->target_combos && + (combobox = g_hash_table_lookup (priv->target_combos, name)) != NULL) + { + g_autofree gchar *id = NULL; + id = g_strdup_printf ("%d.%d", major, minor); + gtk_combo_box_set_active_id(GTK_COMBO_BOX(combobox), id); + } + } + + glade_project_verify (priv->project, FALSE, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED); + + priv->ignore_ui_cb = FALSE; +} + +static void +project_domain_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + const gchar *domain; + + priv->ignore_ui_cb = TRUE; + + domain = glade_project_get_translation_domain (priv->project); + gtk_entry_set_text (GTK_ENTRY (priv->domain_entry), domain ? domain : ""); + + priv->ignore_ui_cb = FALSE; +} + +static void +project_path_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties) +{ + g_autofree gchar *name = NULL; + GtkHeaderBar *headerbar; + const gchar *path; + + if (!(headerbar = GTK_HEADER_BAR (gtk_dialog_get_header_bar (GTK_DIALOG (properties))))) + return; + + name = glade_project_get_name (project); + + gtk_header_bar_set_title (headerbar, name); + + if ((path = glade_project_get_path (project))) + { + g_autofree gchar *dirname = g_path_get_dirname (path); + const gchar *home = g_get_home_dir (); + + if (g_str_has_prefix (dirname, home)) + { + char *subtitle = &dirname[g_utf8_strlen (home, -1) - 1]; + subtitle[0] = '~'; + gtk_header_bar_set_subtitle (headerbar, subtitle); + } + else + gtk_header_bar_set_subtitle (headerbar, dirname); + } +} + +static void +project_resource_path_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + priv->ignore_ui_cb = TRUE; + update_prefs_for_resource_path (properties); + priv->ignore_ui_cb = FALSE; +} + +static void +project_template_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + GladeWidget *gwidget; + + priv->ignore_ui_cb = TRUE; + + if ((gwidget = glade_project_get_template (priv->project))) + { + gtk_combo_box_set_active_id (GTK_COMBO_BOX (priv->template_combobox), + glade_widget_get_name (gwidget)); + + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->template_checkbutton), TRUE); + gtk_widget_set_sensitive (priv->template_combobox, TRUE); + } + else + { + gtk_combo_box_set_active_id (GTK_COMBO_BOX (priv->template_combobox), NULL); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->template_checkbutton), FALSE); + gtk_widget_set_sensitive (priv->template_combobox, FALSE); + } + + priv->ignore_ui_cb = FALSE; +} + +static void +project_license_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + + priv->ignore_ui_cb = TRUE; + gtk_text_buffer_set_text (priv->license_textbuffer, + glade_project_get_license (project), + -1); + priv->ignore_ui_cb = FALSE; +} + +static void +project_css_provider_path_changed (GladeProject *project, + GParamSpec *pspec, + GladeProjectProperties *properties) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(properties); + const gchar *filename = glade_project_get_css_provider_path (project); + GtkFileChooser *chooser = GTK_FILE_CHOOSER (priv->css_filechooser); + + priv->ignore_ui_cb = TRUE; + + if (filename) + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->css_checkbutton), TRUE); + gtk_widget_set_sensitive (priv->css_filechooser, TRUE); + gtk_file_chooser_set_filename (chooser, filename); + } + else + { + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (priv->css_checkbutton), FALSE); + gtk_widget_set_sensitive (priv->css_filechooser, FALSE); + gtk_file_chooser_unselect_all (chooser); + } + + priv->ignore_ui_cb = FALSE; +} + +/* Private API */ +void +_glade_project_properties_set_warnings (GladeProjectProperties *props, + const gchar *warnings) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(props); + GtkTextIter iter; + + gtk_text_buffer_set_text (priv->warnings_textbuffer, "", -1); + gtk_text_buffer_get_start_iter (priv->warnings_textbuffer, &iter); + gtk_text_buffer_insert_markup (priv->warnings_textbuffer, &iter, + warnings ? warnings : "", -1); +} + +void +_glade_project_properties_set_license_data (GladeProjectProperties *props, + const gchar *license, + const gchar *name, + const gchar *description, + const gchar *copyright, + const gchar *authors) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(props); + + if (!license || + !gtk_combo_box_set_active_id (priv->license_comboboxtext, license)) + { + gtk_combo_box_set_active_id (priv->license_comboboxtext, "other"); + name = description = copyright = authors = ""; + license = "other"; + } + + gtk_entry_buffer_set_text (priv->name_entrybuffer, name ? name : "", -1); + gtk_entry_buffer_set_text (priv->description_entrybuffer, description ? description : "", -1); + + gtk_text_buffer_set_text (priv->copyright_textbuffer, copyright ? copyright : "", -1); + gtk_text_buffer_set_text (priv->authors_textbuffer, authors ? authors : "", -1); + + gpp_update_license (props, gpp_get_license_from_id (license)); +} + +void +_glade_project_properties_get_license_data (GladeProjectProperties *props, + gchar **license, + gchar **name, + gchar **description, + gchar **copyright, + gchar **authors) +{ + GladeProjectPropertiesPrivate *priv = GLADE_PROJECT_PROPERTIES_PRIVATE(props); + const gchar *id = gtk_combo_box_get_active_id (priv->license_comboboxtext); + + if (!g_strcmp0 (id, "other")) + { + *license = *name = *description = *copyright = *authors = NULL; + return; + } + + *license = g_strdup (id); + *name = g_strdup (gtk_entry_buffer_get_text (priv->name_entrybuffer)); + *description = g_strdup (gtk_entry_buffer_get_text (priv->description_entrybuffer)); + + g_object_get (priv->copyright_textbuffer, "text", copyright, NULL); + g_object_get (priv->authors_textbuffer, "text", authors, NULL); +} + +/****************************************************** + * API * + ******************************************************/ +GtkWidget * +glade_project_properties_new (GladeProject *project) +{ + return g_object_new (GLADE_TYPE_PROJECT_PROPERTIES, + "use-header-bar", TRUE, + "project", project, + NULL); +} diff --git a/gladeui/glade-project-properties.h b/gladeui/glade-project-properties.h new file mode 100644 index 0000000..fe54b08 --- /dev/null +++ b/gladeui/glade-project-properties.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#ifndef __GLADE_PROJECT_PROPERTIES_H__ +#define __GLADE_PROJECT_PROPERTIES_H__ + +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PROJECT_PROPERTIES (glade_project_properties_get_type ()) +G_DECLARE_FINAL_TYPE (GladeProjectProperties, glade_project_properties, GLADE, PROJECT_PROPERTIES, GtkDialog) + +GtkWidget *glade_project_properties_new (GladeProject *project); + +G_END_DECLS + +#endif /* __GLADE_PROJECT_PROPERTIES_H__ */ diff --git a/gladeui/glade-project-properties.ui b/gladeui/glade-project-properties.ui new file mode 100644 index 0000000..af3fdae --- /dev/null +++ b/gladeui/glade-project-properties.ui @@ -0,0 +1,714 @@ + + + + + + + + + + + + + + + + + + text/css + + + *.css + + + + + True + False + view-refresh-symbolic + + + + + + + + + + + + + + + + + + diff --git a/gladeui/glade-project.c b/gladeui/glade-project.c new file mode 100644 index 0000000..829b45a --- /dev/null +++ b/gladeui/glade-project.c @@ -0,0 +1,5520 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2008 Tristan Van Berkom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Chema Celorio + * Tristan Van Berkom + */ + +#include + +/** + * SECTION:glade-project + * @Short_Description: The Glade document hub and Load/Save interface. + * + * This object owns all project objects and is responsable for loading and + * saving the glade document, you can monitor the project state via this + * object and its signals. + */ + +#include +#include +#include +#include +#include + +#include "glade.h" +#include "gladeui-enum-types.h" +#include "glade-widget.h" +#include "glade-id-allocator.h" +#include "glade-app.h" +#include "glade-marshallers.h" +#include "glade-catalog.h" + +#include "glade-preview.h" + +#include "glade-project.h" +#include "glade-command.h" +#include "glade-name-context.h" +#include "glade-object-stub.h" +#include "glade-project-properties.h" +#include "glade-dnd.h" +#include "glade-private.h" +#include "glade-tsort.h" + +static void glade_project_target_version_for_adaptor + (GladeProject *project, + GladeWidgetAdaptor *adaptor, + gint *major, + gint *minor); +static void glade_project_verify_properties (GladeWidget *widget); +static void glade_project_verify_project_for_ui (GladeProject *project); +static void glade_project_verify_adaptor (GladeProject *project, + GladeWidgetAdaptor *adaptor, + const gchar *path_name, + GString *string, + GladeVerifyFlags flags, + gboolean forwidget, + GladeSupportMask *mask); +static void glade_project_set_readonly (GladeProject *project, + gboolean readonly); +static void glade_project_set_modified (GladeProject *project, + gboolean modified); + +static void glade_project_model_iface_init (GtkTreeModelIface *iface); + +static void glade_project_drag_source_init (GtkTreeDragSourceIface *iface); + +struct _GladeProjectPrivate +{ + gchar *path; /* The full canonical path of the glade file for this project */ + + gchar *translation_domain; /* The project translation domain */ + + gint unsaved_number; /* A unique number for this project if it is untitled */ + + GladeWidgetAdaptor *add_item; /* The next item to add to the project. */ + + GList *tree; /* List of toplevel Objects in this projects */ + GList *objects; /* List of all objects in this project */ + GtkTreeModel *model; /* GtkTreeStore used as proxy model */ + + GList *selection; /* We need to keep the selection in the project + * because we have multiple projects and when the + * user switchs between them, he will probably + * not want to loose the selection. This is a list + * of #GtkWidget items. + */ + guint selection_changed_id; + + GladeNameContext *widget_names; /* Context for uniqueness of names */ + + + GList *undo_stack; /* A stack with the last executed commands */ + GList *prev_redo_item; /* Points to the item previous to the redo items */ + + GList *first_modification; /* we record the first modification, so that we + * can set "modification" to FALSE when we + * undo this modification + */ + + GladeWidget *template; /* The template widget */ + + gchar *license; /* License for this project (will be saved as a comment) */ + + GList *comments; /* XML comments, Glade will preserve whatever comment was + * in file before the root element, so users can delete or change it. + */ + + time_t mtime; /* last UTC modification time of file, or 0 if it could not be read */ + + GHashTable *target_versions_major; /* target versions by catalog */ + GHashTable *target_versions_minor; /* target versions by catalog */ + + gchar *resource_path; /* Indicates where to load resources from for this project + * (full or relative path, null means project directory). + */ + + gchar *css_provider_path; /* The custom css to use for this project */ + GtkCssProvider *css_provider; + GFileMonitor *css_monitor; + + GList *unknown_catalogs; /* List of CatalogInfo catalogs */ + + GtkWidget *prefs_dialog; + + /* Store previews, so we can kill them on close */ + GHashTable *previews; + + /* For the loading progress bars ("load-progress" signal) */ + gint progress_step; + gint progress_full; + + /* Flags */ + guint load_cancel : 1; + guint first_modification_is_na : 1; /* indicates that the first_modification item has been lost */ + guint has_selection : 1; /* Whether the project has a selection */ + guint readonly : 1; /* A flag that is set if the project is readonly */ + guint loading : 1; /* A flags that is set when the project is loading */ + guint modified : 1; /* A flag that is set when a project has unsaved modifications + * if this flag is not set we don't have to query + * for confirmation after a close or exit is + * requested + */ + guint writing_preview : 1; /* During serialization, if we are serializing for a preview */ + guint pointer_mode : 3; /* The currently effective GladePointerMode */ +}; + +typedef struct +{ + gchar *catalog; + gint position; +} CatalogInfo; + + +enum +{ + ADD_WIDGET, + REMOVE_WIDGET, + WIDGET_NAME_CHANGED, + SELECTION_CHANGED, + CLOSE, + CHANGED, + PARSE_BEGAN, + PARSE_FINISHED, + TARGETS_CHANGED, + LOAD_PROGRESS, + WIDGET_VISIBILITY_CHANGED, + ADD_SIGNAL_HANDLER, + REMOVE_SIGNAL_HANDLER, + CHANGE_SIGNAL_HANDLER, + ACTIVATE_SIGNAL_HANDLER, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_MODIFIED, + PROP_HAS_SELECTION, + PROP_PATH, + PROP_READ_ONLY, + PROP_ADD_ITEM, + PROP_POINTER_MODE, + PROP_TRANSLATION_DOMAIN, + PROP_TEMPLATE, + PROP_RESOURCE_PATH, + PROP_LICENSE, + PROP_CSS_PROVIDER_PATH, + N_PROPERTIES +}; + +static GParamSpec *glade_project_props[N_PROPERTIES]; +static guint glade_project_signals[LAST_SIGNAL] = { 0 }; +static GladeIDAllocator *unsaved_number_allocator = NULL; + + +#define GLADE_XML_COMMENT "Generated with "PACKAGE_NAME +#define GLADE_PROJECT_LARGE_PROJECT 40 + +#define VALID_ITER(project, iter) \ + ((iter)!= NULL && G_IS_OBJECT ((iter)->user_data) && \ + ((GladeProject*)(project))->priv->stamp == (iter)->stamp) + +G_DEFINE_TYPE_WITH_CODE (GladeProject, glade_project, G_TYPE_OBJECT, + G_ADD_PRIVATE (GladeProject) + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_MODEL, + glade_project_model_iface_init) + G_IMPLEMENT_INTERFACE (GTK_TYPE_TREE_DRAG_SOURCE, + glade_project_drag_source_init)) + + +/******************************************************************* + GObjectClass + *******************************************************************/ +static GladeIDAllocator *get_unsaved_number_allocator (void) +{ + if (unsaved_number_allocator == NULL) + unsaved_number_allocator = glade_id_allocator_new (); + + return unsaved_number_allocator; +} + +static void +glade_project_list_unref (GList *original_list) +{ + GList *l; + for (l = original_list; l; l = l->next) + g_object_unref (G_OBJECT (l->data)); + + if (original_list != NULL) + g_list_free (original_list); +} + +static void +glade_project_dispose (GObject *object) +{ + GladeProject *project = GLADE_PROJECT (object); + GladeProjectPrivate *priv = project->priv; + + /* Emit close signal */ + g_signal_emit (object, glade_project_signals[CLOSE], 0); + + /* Disconnect from model */ +#define MODEL_DISCONNECT(func) g_signal_handlers_disconnect_by_func (priv->model, G_CALLBACK (func), project) + MODEL_DISCONNECT (gtk_tree_model_row_changed); + MODEL_DISCONNECT (gtk_tree_model_row_inserted); + MODEL_DISCONNECT (gtk_tree_model_row_has_child_toggled); + MODEL_DISCONNECT (gtk_tree_model_row_deleted); + MODEL_DISCONNECT (gtk_tree_model_rows_reordered); + + /* Destroy preferences dialog */ + g_clear_pointer (&priv->prefs_dialog, gtk_widget_destroy); + + /* Clear selection */ + g_clear_pointer (&priv->selection, g_list_free); + g_clear_handle_id (&priv->selection_changed_id, g_source_remove); + + /* Clear undo/redo stack */ + g_clear_pointer (&priv->undo_stack, glade_project_list_unref); + + /* NOTE: prev_redo_item and first_modification always point to undo_stack + * So we should never try to free it + */ + priv->prev_redo_item = NULL; + priv->first_modification = NULL; + + /* Destroy running previews */ + if (priv->previews) + { + g_hash_table_destroy (priv->previews); + priv->previews = NULL; + } + + g_clear_object (&priv->css_provider); + g_clear_object (&priv->css_monitor); + + /* Remove objects from the project */ + while (priv->tree) + glade_project_remove_object (project, priv->tree->data); + + while (priv->objects) + glade_project_remove_object (project, priv->objects->data); + + g_assert (priv->tree == NULL); + g_assert (priv->objects == NULL); + g_assert (gtk_tree_model_iter_n_children (priv->model, NULL) == 0); + + if (priv->unknown_catalogs) + { + GList *l; + + for (l = priv->unknown_catalogs; l; l = g_list_next (l)) + { + CatalogInfo *data = l->data; + g_free (data->catalog); + g_free (data); + } + + g_list_free (priv->unknown_catalogs); + priv->unknown_catalogs = NULL; + } + + g_object_unref (priv->model); + + G_OBJECT_CLASS (glade_project_parent_class)->dispose (object); +} + +static void +glade_project_finalize (GObject *object) +{ + GladeProject *project = GLADE_PROJECT (object); + GladeProjectPrivate *priv = project->priv; + + g_free (priv->path); + g_free (priv->license); + g_free (priv->css_provider_path); + + if (priv->comments) + g_list_free_full (priv->comments, g_free); + + if (priv->unsaved_number > 0) + glade_id_allocator_release (get_unsaved_number_allocator (), + priv->unsaved_number); + + g_hash_table_destroy (priv->target_versions_major); + g_hash_table_destroy (priv->target_versions_minor); + + glade_name_context_destroy (priv->widget_names); + + G_OBJECT_CLASS (glade_project_parent_class)->finalize (object); +} + +static void +glade_project_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeProject *project = GLADE_PROJECT (object); + + switch (prop_id) + { + case PROP_MODIFIED: + g_value_set_boolean (value, project->priv->modified); + break; + case PROP_HAS_SELECTION: + g_value_set_boolean (value, project->priv->has_selection); + break; + case PROP_PATH: + g_value_set_string (value, project->priv->path); + break; + case PROP_READ_ONLY: + g_value_set_boolean (value, project->priv->readonly); + break; + case PROP_ADD_ITEM: + g_value_set_object (value, project->priv->add_item); + break; + case PROP_POINTER_MODE: + g_value_set_enum (value, project->priv->pointer_mode); + break; + case PROP_TRANSLATION_DOMAIN: + g_value_set_string (value, project->priv->translation_domain); + break; + case PROP_TEMPLATE: + g_value_set_object (value, project->priv->template); + break; + case PROP_RESOURCE_PATH: + g_value_set_string (value, project->priv->resource_path); + break; + case PROP_LICENSE: + g_value_set_string (value, project->priv->license); + break; + case PROP_CSS_PROVIDER_PATH: + g_value_set_string (value, project->priv->css_provider_path); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_project_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + switch (prop_id) + { + case PROP_TRANSLATION_DOMAIN: + glade_project_set_translation_domain (GLADE_PROJECT (object), + g_value_get_string (value)); + break; + case PROP_TEMPLATE: + glade_project_set_template (GLADE_PROJECT (object), + g_value_get_object (value)); + break; + case PROP_RESOURCE_PATH: + glade_project_set_resource_path (GLADE_PROJECT (object), + g_value_get_string (value)); + break; + case PROP_LICENSE: + glade_project_set_license (GLADE_PROJECT (object), + g_value_get_string (value)); + break; + case PROP_CSS_PROVIDER_PATH: + glade_project_set_css_provider_path (GLADE_PROJECT (object), + g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/******************************************************************* + GladeProjectClass + *******************************************************************/ +static void +glade_project_walk_back (GladeProject *project) +{ + if (project->priv->prev_redo_item) + project->priv->prev_redo_item = project->priv->prev_redo_item->prev; +} + +static void +glade_project_walk_forward (GladeProject *project) +{ + if (project->priv->prev_redo_item) + project->priv->prev_redo_item = project->priv->prev_redo_item->next; + else + project->priv->prev_redo_item = project->priv->undo_stack; +} + +static void +glade_project_undo_impl (GladeProject *project) +{ + GladeCommand *cmd, *next_cmd; + + while ((cmd = glade_project_next_undo_item (project)) != NULL) + { + glade_command_undo (cmd); + + glade_project_walk_back (project); + + g_signal_emit (G_OBJECT (project), + glade_project_signals[CHANGED], 0, cmd, FALSE); + + if ((next_cmd = glade_project_next_undo_item (project)) != NULL && + (glade_command_group_id (next_cmd) == 0 || + glade_command_group_id (next_cmd) != glade_command_group_id (cmd))) + break; + } +} + +static void +glade_project_redo_impl (GladeProject *project) +{ + GladeCommand *cmd, *next_cmd; + + while ((cmd = glade_project_next_redo_item (project)) != NULL) + { + glade_command_execute (cmd); + + glade_project_walk_forward (project); + + g_signal_emit (G_OBJECT (project), + glade_project_signals[CHANGED], 0, cmd, TRUE); + + if ((next_cmd = glade_project_next_redo_item (project)) != NULL && + (glade_command_group_id (next_cmd) == 0 || + glade_command_group_id (next_cmd) != glade_command_group_id (cmd))) + break; + } +} + +static GladeCommand * +glade_project_next_undo_item_impl (GladeProject *project) +{ + GList *l; + + if ((l = project->priv->prev_redo_item) == NULL) + return NULL; + + return GLADE_COMMAND (l->data); +} + +static GladeCommand * +glade_project_next_redo_item_impl (GladeProject *project) +{ + GList *l; + + if ((l = project->priv->prev_redo_item) == NULL) + return project->priv->undo_stack ? + GLADE_COMMAND (project->priv->undo_stack->data) : NULL; + else + return l->next ? GLADE_COMMAND (l->next->data) : NULL; +} + +static GList * +glade_project_free_undo_item (GladeProject *project, GList *item) +{ + g_assert (item->data); + + if (item == project->priv->first_modification) + project->priv->first_modification_is_na = TRUE; + + g_object_unref (G_OBJECT (item->data)); + + return g_list_next (item); +} + +static void +glade_project_push_undo_impl (GladeProject *project, GladeCommand *cmd) +{ + GladeProjectPrivate *priv = project->priv; + GList *tmp_redo_item; + + /* We should now free all the "redo" items */ + tmp_redo_item = g_list_next (priv->prev_redo_item); + while (tmp_redo_item) + tmp_redo_item = glade_project_free_undo_item (project, tmp_redo_item); + + if (priv->prev_redo_item) + { + g_list_free (g_list_next (priv->prev_redo_item)); + priv->prev_redo_item->next = NULL; + } + else + { + g_list_free (priv->undo_stack); + priv->undo_stack = NULL; + } + + /* Try to unify only if group depth is 0 and the project has not been recently saved */ + if (glade_command_get_group_depth () == 0 && + priv->prev_redo_item != NULL && + project->priv->prev_redo_item != project->priv->first_modification) + { + GladeCommand *cmd1 = priv->prev_redo_item->data; + + if (glade_command_unifies (cmd1, cmd)) + { + glade_command_collapse (cmd1, cmd); + g_object_unref (cmd); + + if (glade_command_unifies (cmd1, NULL)) + { + tmp_redo_item = priv->prev_redo_item; + glade_project_walk_back (project); + glade_project_free_undo_item (project, tmp_redo_item); + priv->undo_stack = + g_list_delete_link (priv->undo_stack, tmp_redo_item); + + cmd1 = NULL; + } + + g_signal_emit (G_OBJECT (project), + glade_project_signals[CHANGED], 0, cmd1, TRUE); + return; + } + } + + /* and then push the new undo item */ + priv->undo_stack = g_list_append (priv->undo_stack, cmd); + + if (project->priv->prev_redo_item == NULL) + priv->prev_redo_item = priv->undo_stack; + else + priv->prev_redo_item = g_list_next (priv->prev_redo_item); + + g_signal_emit (G_OBJECT (project), + glade_project_signals[CHANGED], 0, cmd, TRUE); +} + +static inline gchar * +glade_preview_get_pid_as_str (GladePreview *preview) +{ +#ifdef G_OS_WIN32 + return g_strdup_printf ("%p", glade_preview_get_pid (preview)); +#else + return g_strdup_printf ("%d", glade_preview_get_pid (preview)); +#endif +} + +static void +glade_project_preview_exits (GladePreview *preview, GladeProject *project) +{ + gchar *pidstr; + + pidstr = glade_preview_get_pid_as_str (preview); + preview = g_hash_table_lookup (project->priv->previews, pidstr); + + if (preview) + g_hash_table_remove (project->priv->previews, pidstr); + + g_free (pidstr); +} + +static void +glade_project_destroy_preview (gpointer data) +{ + GladePreview *preview = GLADE_PREVIEW (data); + GladeWidget *gwidget; + + gwidget = glade_preview_get_widget (preview); + g_object_set_data (G_OBJECT (gwidget), "preview", NULL); + + g_signal_handlers_disconnect_by_func (preview, + G_CALLBACK (glade_project_preview_exits), + g_object_get_data (G_OBJECT (preview), "project")); + g_object_unref (preview); +} + +static void +glade_project_changed_impl (GladeProject *project, + GladeCommand *command, + gboolean forward) +{ + if (!project->priv->loading) + { + /* if this command is the first modification to cause the project + * to have unsaved changes, then we can now flag the project as unmodified + */ + if (!project->priv->first_modification_is_na && + project->priv->prev_redo_item == project->priv->first_modification) + glade_project_set_modified (project, FALSE); + else + glade_project_set_modified (project, TRUE); + } +} + +static void +glade_project_set_css_provider_forall (GtkWidget *widget, gpointer data) +{ + if (GLADE_IS_PLACEHOLDER (widget) || GLADE_IS_OBJECT_STUB (widget)) + return; + + gtk_style_context_add_provider (gtk_widget_get_style_context (widget), + GTK_STYLE_PROVIDER (data), + GTK_STYLE_PROVIDER_PRIORITY_APPLICATION); + + if (GTK_IS_CONTAINER (widget)) + gtk_container_forall (GTK_CONTAINER (widget), glade_project_set_css_provider_forall, data); +} + +static void +glade_project_add_object_impl (GladeProject *project, GladeWidget *gwidget) +{ + GladeProjectPrivate *priv = project->priv; + GObject *widget = glade_widget_get_object (gwidget); + + if (!priv->css_provider || !GTK_IS_WIDGET (widget)) + return; + + glade_project_set_css_provider_forall (GTK_WIDGET (widget), priv->css_provider); +} + +/******************************************************************* + Class Initializers + *******************************************************************/ +static void +glade_project_init (GladeProject *project) +{ + GladeProjectPrivate *priv; + GList *list; + + project->priv = priv = glade_project_get_instance_private (project); + + priv->path = NULL; + priv->model = GTK_TREE_MODEL (gtk_tree_store_new (1, G_TYPE_OBJECT)); + + g_signal_connect_swapped (priv->model, "row-changed", + G_CALLBACK (gtk_tree_model_row_changed), + project); + g_signal_connect_swapped (priv->model, "row-inserted", + G_CALLBACK (gtk_tree_model_row_inserted), + project); + g_signal_connect_swapped (priv->model, "row-has-child-toggled", + G_CALLBACK (gtk_tree_model_row_has_child_toggled), + project); + g_signal_connect_swapped (priv->model, "row-deleted", + G_CALLBACK (gtk_tree_model_row_deleted), + project); + g_signal_connect_swapped (priv->model, "rows-reordered", + G_CALLBACK (gtk_tree_model_rows_reordered), + project); + + priv->readonly = FALSE; + priv->tree = NULL; + priv->selection = NULL; + priv->has_selection = FALSE; + priv->undo_stack = NULL; + priv->prev_redo_item = NULL; + priv->first_modification = NULL; + priv->first_modification_is_na = FALSE; + priv->unknown_catalogs = NULL; + + priv->previews = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, + glade_project_destroy_preview); + + priv->widget_names = glade_name_context_new (); + + priv->unsaved_number = + glade_id_allocator_allocate (get_unsaved_number_allocator ()); + + priv->target_versions_major = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, NULL); + priv->target_versions_minor = g_hash_table_new_full (g_str_hash, + g_str_equal, + g_free, NULL); + + for (list = glade_app_get_catalogs (); list; list = list->next) + { + GladeCatalog *catalog = list->data; + + /* Set default target to catalog version */ + glade_project_set_target_version (project, + glade_catalog_get_name (catalog), + glade_catalog_get_major_version + (catalog), + glade_catalog_get_minor_version + (catalog)); + } + + priv->prefs_dialog = glade_project_properties_new (project); +} + +static void +glade_project_class_init (GladeProjectClass *klass) +{ + GObjectClass *object_class; + + object_class = G_OBJECT_CLASS (klass); + + object_class->get_property = glade_project_get_property; + object_class->set_property = glade_project_set_property; + object_class->finalize = glade_project_finalize; + object_class->dispose = glade_project_dispose; + + klass->add_object = glade_project_add_object_impl; + klass->remove_object = NULL; + klass->undo = glade_project_undo_impl; + klass->redo = glade_project_redo_impl; + klass->next_undo_item = glade_project_next_undo_item_impl; + klass->next_redo_item = glade_project_next_redo_item_impl; + klass->push_undo = glade_project_push_undo_impl; + + klass->widget_name_changed = NULL; + klass->selection_changed = NULL; + klass->close = NULL; + klass->changed = glade_project_changed_impl; + + /** + * GladeProject::add-widget: + * @gladeproject: the #GladeProject which received the signal. + * @arg1: the #GladeWidget that was added to @gladeproject. + * + * Emitted when a widget is added to a project. + */ + glade_project_signals[ADD_WIDGET] = + g_signal_new ("add_widget", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeProjectClass, add_object), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, GLADE_TYPE_WIDGET); + + /** + * GladeProject::remove-widget: + * @gladeproject: the #GladeProject which received the signal. + * @arg1: the #GladeWidget that was removed from @gladeproject. + * + * Emitted when a widget is removed from a project. + */ + glade_project_signals[REMOVE_WIDGET] = + g_signal_new ("remove_widget", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeProjectClass, remove_object), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, GLADE_TYPE_WIDGET); + + + /** + * GladeProject::widget-name-changed: + * @gladeproject: the #GladeProject which received the signal. + * @arg1: the #GladeWidget who's name changed. + * + * Emitted when @gwidget's name changes. + */ + glade_project_signals[WIDGET_NAME_CHANGED] = + g_signal_new ("widget_name_changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeProjectClass, widget_name_changed), + NULL, NULL, + g_cclosure_marshal_VOID__OBJECT, + G_TYPE_NONE, 1, GLADE_TYPE_WIDGET); + + + /** + * GladeProject::selection-changed: + * @gladeproject: the #GladeProject which received the signal. + * + * Emitted when @gladeproject selection list changes. + */ + glade_project_signals[SELECTION_CHANGED] = + g_signal_new ("selection_changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeProjectClass, selection_changed), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + + /** + * GladeProject::close: + * @gladeproject: the #GladeProject which received the signal. + * + * Emitted when a project is closing (a good time to clean up + * any associated resources). + */ + glade_project_signals[CLOSE] = + g_signal_new ("close", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladeProjectClass, close), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GladeProject::changed: + * @gladeproject: the #GladeProject which received the signal. + * @arg1: the #GladeCommand that was executed + * @arg2: whether the command was executed or undone. + * + * Emitted when a @gladeproject's state changes via a #GladeCommand. + */ + glade_project_signals[CHANGED] = + g_signal_new ("changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GladeProjectClass, changed), + NULL, NULL, + _glade_marshal_VOID__OBJECT_BOOLEAN, + G_TYPE_NONE, 2, GLADE_TYPE_COMMAND, G_TYPE_BOOLEAN); + + /** + * GladeProject::parse-began: + * @gladeproject: the #GladeProject which received the signal. + * + * Emitted when @gladeproject parsing starts. + */ + glade_project_signals[PARSE_BEGAN] = + g_signal_new ("parse-began", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GladeProject::parse-finished: + * @gladeproject: the #GladeProject which received the signal. + * + * Emitted when @gladeproject parsing has finished. + */ + glade_project_signals[PARSE_FINISHED] = + g_signal_new ("parse-finished", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + G_STRUCT_OFFSET (GladeProjectClass, parse_finished), + NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GladeProject::targets-changed: + * @gladeproject: the #GladeProject which received the signal. + * + * Emitted when @gladeproject target versions change. + */ + glade_project_signals[TARGETS_CHANGED] = + g_signal_new ("targets-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0); + + /** + * GladeProject::load-progress: + * @gladeproject: the #GladeProject which received the signal. + * @objects_total: the total amount of objects to load + * @objects_loaded: the current amount of loaded objects + * + * Emitted while @project is loading. + */ + glade_project_signals[LOAD_PROGRESS] = + g_signal_new ("load-progress", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + _glade_marshal_VOID__INT_INT, + G_TYPE_NONE, 2, G_TYPE_INT, G_TYPE_INT); + + /** + * GladeProject::widget-visibility-changed: + * @gladeproject: the #GladeProject which received the signal. + * @widget: the widget that its visibility changed + * @visible: the current visibility of the widget + * + * Emitted when the visibility of a widget changed + */ + glade_project_signals[WIDGET_VISIBILITY_CHANGED] = + g_signal_new ("widget-visibility-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_FIRST, + 0, NULL, NULL, + _glade_marshal_VOID__OBJECT_BOOLEAN, + G_TYPE_NONE, 2, GLADE_TYPE_WIDGET, G_TYPE_BOOLEAN); + + /** + * GladeProject::add-signal-handler: + * @gladeproject: the #GladeProject which received the signal. + * @gladewidget: the #GladeWidget + * @signal: the #GladeSignal that was added to @gladewidget. + */ + glade_project_signals[ADD_SIGNAL_HANDLER] = + g_signal_new ("add-signal-handler", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _glade_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, + 2, + GLADE_TYPE_WIDGET, + GLADE_TYPE_SIGNAL); + + /** + * GladeProject::remove-signal-handler: + * @gladeproject: the #GladeProject which received the signal. + * @gladewidget: the #GladeWidget + * @signal: the #GladeSignal that was removed from @gladewidget. + */ + glade_project_signals[REMOVE_SIGNAL_HANDLER] = + g_signal_new ("remove-signal-handler", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _glade_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, + 2, + GLADE_TYPE_WIDGET, + GLADE_TYPE_SIGNAL); + + /** + * GladeProject::change-signal-handler: + * @gladeproject: the #GladeProject which received the signal. + * @gladewidget: the #GladeWidget + * @old_signal: the old #GladeSignal that changed + * @new_signal: the new #GladeSignal + */ + glade_project_signals[CHANGE_SIGNAL_HANDLER] = + g_signal_new ("change-signal-handler", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _glade_marshal_VOID__OBJECT_OBJECT_OBJECT, + G_TYPE_NONE, + 3, + GLADE_TYPE_WIDGET, + GLADE_TYPE_SIGNAL, + GLADE_TYPE_SIGNAL); + + /** + * GladeProject::activate-signal-handler: + * @gladeproject: the #GladeProject which received the signal. + * @gladewidget: the #GladeWidget + * @signal: the #GladeSignal that was activated + */ + glade_project_signals[ACTIVATE_SIGNAL_HANDLER] = + g_signal_new ("activate-signal-handler", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + _glade_marshal_VOID__OBJECT_OBJECT, + G_TYPE_NONE, + 2, + GLADE_TYPE_WIDGET, + GLADE_TYPE_SIGNAL); + + glade_project_props[PROP_MODIFIED] = + g_param_spec_boolean ("modified", + "Modified", + _("Whether project has been modified since it was last saved"), + FALSE, + G_PARAM_READABLE); + + glade_project_props[PROP_HAS_SELECTION] = + g_param_spec_boolean ("has-selection", + _("Has Selection"), + _("Whether project has a selection"), + FALSE, + G_PARAM_READABLE); + + glade_project_props[PROP_PATH] = + g_param_spec_string ("path", + _("Path"), + _("The filesystem path of the project"), + NULL, + G_PARAM_READABLE); + + glade_project_props[PROP_READ_ONLY] = + g_param_spec_boolean ("read-only", + _("Read Only"), + _("Whether project is read-only"), + FALSE, + G_PARAM_READABLE); + + glade_project_props[PROP_ADD_ITEM] = + g_param_spec_object ("add-item", + _("Add Item"), + _("The current item to add to the project"), + GLADE_TYPE_WIDGET_ADAPTOR, + G_PARAM_READABLE); + + glade_project_props[PROP_POINTER_MODE] = + g_param_spec_enum ("pointer-mode", + _("Pointer Mode"), + _("The currently effective GladePointerMode"), + GLADE_TYPE_POINTER_MODE, + GLADE_POINTER_SELECT, + G_PARAM_READABLE); + + glade_project_props[PROP_TRANSLATION_DOMAIN] = + g_param_spec_string ("translation-domain", + _("Translation Domain"), + _("The project translation domain"), + NULL, + G_PARAM_READWRITE); + + glade_project_props[PROP_TEMPLATE] = + g_param_spec_object ("template", + _("Template"), + _("The project's template widget, if any"), + GLADE_TYPE_WIDGET, + G_PARAM_READWRITE); + + glade_project_props[PROP_RESOURCE_PATH] = + g_param_spec_string ("resource-path", + _("Resource Path"), + _("Path to load images and resources in Glade's runtime"), + NULL, + G_PARAM_READWRITE); + + glade_project_props[PROP_LICENSE] = + g_param_spec_string ("license", + _("License"), + _("License for this project, it will be added as a document level comment."), + NULL, + G_PARAM_READWRITE); + + glade_project_props[PROP_CSS_PROVIDER_PATH] = + g_param_spec_string ("css-provider-path", + _("CSS Provider Path"), + _("Path to use as the custom CSS provider for this project."), + NULL, + G_PARAM_READWRITE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, glade_project_props); +} + +/****************************************************************** + * GtkTreeModelIface * + ******************************************************************/ +static GtkTreeModelFlags +glade_project_model_get_flags (GtkTreeModel *model) +{ + return 0; +} + +static gint +glade_project_model_get_n_columns (GtkTreeModel *model) +{ + return GLADE_PROJECT_MODEL_N_COLUMNS; +} + +static GType +glade_project_model_get_column_type (GtkTreeModel *model, gint column) +{ + switch (column) + { + case GLADE_PROJECT_MODEL_COLUMN_ICON_NAME: + return G_TYPE_STRING; + case GLADE_PROJECT_MODEL_COLUMN_NAME: + return G_TYPE_STRING; + case GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME: + return G_TYPE_STRING; + case GLADE_PROJECT_MODEL_COLUMN_OBJECT: + return G_TYPE_OBJECT; + case GLADE_PROJECT_MODEL_COLUMN_MISC: + return G_TYPE_STRING; + case GLADE_PROJECT_MODEL_COLUMN_WARNING: + return G_TYPE_STRING; + default: + g_assert_not_reached (); + return G_TYPE_NONE; + } +} + +static gboolean +glade_project_model_get_iter (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreePath *path) +{ + return gtk_tree_model_get_iter (GLADE_PROJECT (model)->priv->model, iter, path); +} + +static GtkTreePath * +glade_project_model_get_path (GtkTreeModel *model, GtkTreeIter *iter) +{ + return gtk_tree_model_get_path (GLADE_PROJECT (model)->priv->model, iter); +} + +static void +glade_project_model_get_value (GtkTreeModel *model, + GtkTreeIter *iter, + gint column, + GValue *value) +{ + g_autoptr(GladeWidget) widget = NULL; + + gtk_tree_model_get (GLADE_PROJECT (model)->priv->model, iter, 0, &widget, -1); + + value = g_value_init (value, + glade_project_model_get_column_type (model, column)); + + switch (column) + { + case GLADE_PROJECT_MODEL_COLUMN_ICON_NAME: + g_value_set_string (value, glade_widget_adaptor_get_icon_name (glade_widget_get_adaptor (widget))); + break; + case GLADE_PROJECT_MODEL_COLUMN_NAME: + g_value_set_string (value, glade_widget_get_name (widget)); + break; + case GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME: + { + GladeWidgetAdaptor *adaptor = glade_widget_get_adaptor (widget); + g_value_set_static_string (value, glade_widget_adaptor_get_display_name (adaptor)); + break; + } + case GLADE_PROJECT_MODEL_COLUMN_OBJECT: + g_value_set_object (value, glade_widget_get_object (widget)); + break; + case GLADE_PROJECT_MODEL_COLUMN_MISC: + { + gchar *str = NULL, *child_type; + GladeProperty *ref_prop; + + /* special child type / internal child */ + if (glade_widget_get_internal (widget) != NULL) + str = g_strdup_printf (_("(internal %s)"), + glade_widget_get_internal (widget)); + else if ((child_type = + g_object_get_data (glade_widget_get_object (widget), + "special-child-type")) != NULL) + str = g_strdup_printf (_("(%s child)"), child_type); + else if (glade_widget_get_is_composite (widget)) + str = g_strdup_printf (_("(template)")); + else if ((ref_prop = + glade_widget_get_parentless_widget_ref (widget)) != NULL) + { + GladePropertyDef *pdef = glade_property_get_def (ref_prop); + GladeWidget *ref_widget = glade_property_get_widget (ref_prop); + + /* translators: refers to a property named '%s' of widget '%s' */ + str = g_strdup_printf (_("(%s of %s)"), + glade_property_def_get_name (pdef), + glade_widget_get_name (ref_widget)); + } + + g_value_take_string (value, str); + } + break; + case GLADE_PROJECT_MODEL_COLUMN_WARNING: + g_value_set_string (value, glade_widget_support_warning (widget)); + break; + + default: + g_assert_not_reached (); + } +} + +static gboolean +glade_project_model_iter_next (GtkTreeModel *model, GtkTreeIter *iter) +{ + return gtk_tree_model_iter_next (GLADE_PROJECT (model)->priv->model, iter); +} + +static gboolean +glade_project_model_iter_has_child (GtkTreeModel *model, GtkTreeIter *iter) +{ + return gtk_tree_model_iter_has_child (GLADE_PROJECT (model)->priv->model, iter); +} + +static gint +glade_project_model_iter_n_children (GtkTreeModel *model, GtkTreeIter *iter) +{ + return gtk_tree_model_iter_n_children (GLADE_PROJECT (model)->priv->model, iter); +} + +static gboolean +glade_project_model_iter_nth_child (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *parent, + gint nth) +{ + return gtk_tree_model_iter_nth_child (GLADE_PROJECT (model)->priv->model, + iter, parent, nth); +} + +static gboolean +glade_project_model_iter_children (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *parent) +{ + return gtk_tree_model_iter_children (GLADE_PROJECT (model)->priv->model, + iter, parent); +} + +static gboolean +glade_project_model_iter_parent (GtkTreeModel *model, + GtkTreeIter *iter, + GtkTreeIter *child) +{ + return gtk_tree_model_iter_parent (GLADE_PROJECT (model)->priv->model, + iter, child); +} + +static void +glade_project_model_ref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + gtk_tree_model_ref_node (GLADE_PROJECT (model)->priv->model, iter); +} + +static void +glade_project_model_unref_node (GtkTreeModel *model, GtkTreeIter *iter) +{ + gtk_tree_model_unref_node (GLADE_PROJECT (model)->priv->model, iter); +} + + +static void +glade_project_model_iface_init (GtkTreeModelIface *iface) +{ + iface->get_flags = glade_project_model_get_flags; + iface->get_n_columns = glade_project_model_get_n_columns; + iface->get_column_type = glade_project_model_get_column_type; + iface->get_iter = glade_project_model_get_iter; + iface->get_path = glade_project_model_get_path; + iface->get_value = glade_project_model_get_value; + iface->iter_next = glade_project_model_iter_next; + iface->iter_children = glade_project_model_iter_children; + iface->iter_has_child = glade_project_model_iter_has_child; + iface->iter_n_children = glade_project_model_iter_n_children; + iface->iter_nth_child = glade_project_model_iter_nth_child; + iface->iter_parent = glade_project_model_iter_parent; + iface->ref_node = glade_project_model_ref_node; + iface->unref_node = glade_project_model_unref_node; +} + +static gboolean +glade_project_row_draggable (GtkTreeDragSource *drag_source, GtkTreePath *path) +{ + return TRUE; +} + +static gboolean +glade_project_drag_data_delete (GtkTreeDragSource *drag_source, GtkTreePath *path) +{ + return FALSE; +} + +static gboolean +glade_project_drag_data_get (GtkTreeDragSource *drag_source, + GtkTreePath *path, + GtkSelectionData *selection_data) +{ + GtkTreeIter iter; + + if (gtk_tree_model_get_iter (GTK_TREE_MODEL (drag_source), &iter, path)) + { + g_autoptr (GObject) object = NULL; + + gtk_tree_model_get (GTK_TREE_MODEL (drag_source), &iter, + GLADE_PROJECT_MODEL_COLUMN_OBJECT, &object, + -1); + + _glade_dnd_set_data (selection_data, object); + return TRUE; + } + + return FALSE; +} + +static void +glade_project_drag_source_init (GtkTreeDragSourceIface *iface) +{ + iface->row_draggable = glade_project_row_draggable; + iface->drag_data_delete = glade_project_drag_data_delete; + iface->drag_data_get = glade_project_drag_data_get; +} + +/******************************************************************* + Loading project code here + *******************************************************************/ + +/** + * glade_project_new: + * + * Creates a new #GladeProject. + * + * Returns: a new #GladeProject + */ +GladeProject * +glade_project_new (void) +{ + GladeProject *project = g_object_new (GLADE_TYPE_PROJECT, NULL); + return project; +} + +/* Called when finishing loading a glade file to resolve object type properties + */ +static void +glade_project_fix_object_props (GladeProject *project) +{ + GList *l, *ll, *objects; + GValue *value; + GladeWidget *gwidget; + GladeProperty *property; + gchar *txt; + + objects = g_list_copy (project->priv->objects); + for (l = objects; l; l = l->next) + { + gwidget = glade_widget_get_from_gobject (l->data); + + for (ll = glade_widget_get_properties (gwidget); ll; ll = ll->next) + { + GladePropertyDef *def; + + property = GLADE_PROPERTY (ll->data); + def = glade_property_get_def (property); + + if (glade_property_def_is_object (def) && + (txt = g_object_get_data (G_OBJECT (property), + "glade-loaded-object")) != NULL) + { + /* Parse the object list and set the property to it + * (this magically works for both objects & object lists) + */ + value = glade_property_def_make_gvalue_from_string (def, txt, project); + + glade_property_set_value (property, value); + + g_value_unset (value); + g_free (value); + + g_object_set_data (G_OBJECT (property), + "glade-loaded-object", NULL); + } + } + } + g_list_free (objects); +} + +static void +glade_project_fix_template (GladeProject *project) +{ + GList *l; + gboolean composite = FALSE; + + for (l = project->priv->tree; l; l = l->next) + { + GObject *obj = l->data; + GladeWidget *gwidget; + + gwidget = glade_widget_get_from_gobject (obj); + composite = glade_widget_get_is_composite (gwidget); + + if (composite) + { + glade_project_set_template (project, gwidget); + break; + } + } +} + +static gchar * +gp_comment_strip_property (gchar *value, gchar *property) +{ + if (g_str_has_prefix (value, property)) + { + gchar *start = value + strlen (property); + + if (*start == ' ') + start++; + + memmove (value, start, strlen (start) + 1); + return value; + } + + return NULL; +} + +static gchar * +gp_comment_get_content (GladeXmlNode *comment) +{ + gchar *value; + + if (glade_xml_node_is_comment (comment) && + (value = glade_xml_get_content (comment))) + { + gchar *compressed; + + /* Replace NON-BREAKING HYPHEN with regular HYPHEN */ + value = _glade_util_strreplace (g_strstrip (value), TRUE, "\\‑\\‑", "--"); + compressed = g_strcompress (value); + g_free (value); + return compressed; + } + + return NULL; +} + +static gchar * +glade_project_read_requires_from_comment (GladeXmlNode *comment, + guint16 *major, + guint16 *minor) +{ + gchar *value, *requires, *required_lib = NULL; + + if ((value = gp_comment_get_content (comment)) && + (requires = gp_comment_strip_property (value, "interface-requires"))) + { + gchar buffer[128]; + gint maj, min; + + if (sscanf (requires, "%128s %d.%d", buffer, &maj, &min) == 3) + { + if (major) + *major = maj; + if (minor) + *minor = min; + required_lib = g_strdup (buffer); + } + } + g_free (value); + + return required_lib; +} + + +static gboolean +glade_project_read_requires (GladeProject *project, + GladeXmlNode *root_node, + const gchar *path, + gboolean *has_gtk_dep) +{ + + GString *string = g_string_new (NULL); + GladeXmlNode *node; + gboolean loadable = TRUE; + guint16 major, minor; + gint position = 0; + + for (node = glade_xml_node_get_children_with_comments (root_node); + node; node = glade_xml_node_next_with_comments (node)) + { + gchar *required_lib = NULL; + + /* Skip non "requires" tags */ + if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_REQUIRES) || + (required_lib = + glade_project_read_requires_from_comment (node, &major, &minor)))) + continue; + + if (!required_lib) + { + required_lib = + glade_xml_get_property_string_required (node, GLADE_XML_TAG_LIB, + NULL); + glade_xml_get_property_version (node, GLADE_XML_TAG_VERSION, + &major, &minor); + } + + if (!required_lib) + continue; + + /* Dont mention gtk+ as a required lib in + * the generated glade file + */ + if (!glade_catalog_is_loaded (required_lib)) + { + CatalogInfo *data = g_new0(CatalogInfo, 1); + + data->catalog = required_lib; + data->position = position; + + /* Keep a list of unknown catalogs to avoid loosing the requirement */ + project->priv->unknown_catalogs = g_list_append (project->priv->unknown_catalogs, + data); + /* Also keep the version */ + glade_project_set_target_version (project, required_lib, major, minor); + + if (!loadable) + g_string_append (string, ", "); + + g_string_append (string, required_lib); + loadable = FALSE; + } + else + { + if (has_gtk_dep && strcmp (required_lib, "gtk+") == 0) + *has_gtk_dep = TRUE; + + glade_project_set_target_version + (project, required_lib, major, minor); + g_free (required_lib); + } + + position++; + } + + if (!loadable) + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_ERROR, NULL, + _("Failed to load %s.\n" + "The following required catalogs are unavailable: %s"), + path, string->str); + g_string_free (string, TRUE); + return loadable; +} + +static void +update_project_for_resource_path (GladeProject *project) +{ + GladeWidget *widget; + GladeProperty *property; + GList *l, *list; + + for (l = project->priv->objects; l; l = l->next) + { + + widget = glade_widget_get_from_gobject (l->data); + + for (list = glade_widget_get_properties (widget); list; list = list->next) + { + GladePropertyDef *def; + GParamSpec *pspec; + + property = list->data; + def = glade_property_get_def (property); + pspec = glade_property_def_get_pspec (def); + + /* XXX We should have a "resource" flag on properties that need + * to be loaded from the resource path, but that would require + * that they can serialize both ways (custom properties are only + * required to generate unique strings for value comparisons). + */ + if (pspec->value_type == GDK_TYPE_PIXBUF || + pspec->value_type == G_TYPE_FILE) + { + GValue *value; + gchar *string; + + string = glade_property_make_string (property); + value = glade_property_def_make_gvalue_from_string (def, string, project); + + glade_property_set_value (property, value); + + g_value_unset (value); + g_free (value); + g_free (string); + } + } + } +} + +void +glade_project_set_resource_path (GladeProject *project, const gchar *path) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (g_strcmp0 (project->priv->resource_path, path) != 0) + { + g_free (project->priv->resource_path); + project->priv->resource_path = g_strdup (path); + + update_project_for_resource_path (project); + + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_RESOURCE_PATH]); + } +} + +const gchar * +glade_project_get_resource_path (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->resource_path; +} + +void +glade_project_set_license (GladeProject *project, const gchar *license) +{ + GladeProjectPrivate *priv; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + priv = project->priv; + + if ((!license && priv->license) || + (license && g_strcmp0 (priv->license, license) != 0)) + { + g_free (priv->license); + priv->license = g_strdup (license); + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_LICENSE]); + } +} + +const gchar * +glade_project_get_license (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->license; +} + +static void +glade_project_read_comment_properties (GladeProject *project, + GladeXmlNode *root_node) +{ + GladeProjectPrivate *priv = project->priv; + g_autofree gchar *license = NULL, *name = NULL, *description = NULL, *copyright = NULL, *authors = NULL; + GladeXmlNode *node; + + license = name = description = copyright = authors = NULL; + + for (node = glade_xml_node_get_children_with_comments (root_node); + node; node = glade_xml_node_next_with_comments (node)) + { + gchar *val; + + if (!(val = gp_comment_get_content (node))) + continue; + + if (gp_comment_strip_property (val, "interface-local-resource-path")) + glade_project_set_resource_path (project, val); + else if (gp_comment_strip_property (val, "interface-css-provider-path")) + { + if (g_path_is_absolute (val)) + glade_project_set_css_provider_path (project, val); + else + { + gchar *dirname = g_path_get_dirname (priv->path); + gchar *full_path = g_build_filename (dirname, val, NULL); + + glade_project_set_css_provider_path (project, full_path); + + g_free (dirname); + g_free (full_path); + } + } + else if (!license && (license = gp_comment_strip_property (val, "interface-license-type"))) + continue; + else if (!name && (name = gp_comment_strip_property (val, "interface-name"))) + continue; + else if (!description && (description = gp_comment_strip_property (val, "interface-description"))) + continue; + else if (!copyright && (copyright = gp_comment_strip_property (val, "interface-copyright"))) + continue; + else if (!authors && (authors = gp_comment_strip_property (val, "interface-authors"))) + continue; + + g_free (val); + } + + if (priv->prefs_dialog) + _glade_project_properties_set_license_data (GLADE_PROJECT_PROPERTIES (priv->prefs_dialog), + license, + name, + description, + copyright, + authors); +} + +static inline void +glade_project_read_comments (GladeProject *project, GladeXmlNode *root) +{ + GladeProjectPrivate *priv = project->priv; + GladeXmlNode *node; + + /* We only support comments before the root element */ + for (node = glade_xml_node_prev_with_comments (root); node; + node = glade_xml_node_prev_with_comments (node)) + { + if (glade_xml_node_is_comment (node)) + { + gchar *start, *comment = glade_xml_get_content (node); + + /* Ignore leading spaces */ + for (start = comment; *start && g_ascii_isspace (*start); start++); + + /* Do not load generated with glade comment! */ + if (g_str_has_prefix (start, GLADE_XML_COMMENT)) + { + gchar *new_line = g_strstr_len (start, -1, "\n"); + + if (new_line) + glade_project_set_license (project, g_strstrip (new_line)); + + g_free (comment); + continue; + } + + /* Since we are reading in backwards order, + * prepending gives us the right order + */ + priv->comments = g_list_prepend (priv->comments, comment); + } + } +} + +typedef struct +{ + GladeWidget *widget; + gint major; + gint minor; +} VersionData; + +static void +glade_project_introspect_signal_versions (GladeSignal *signal, + VersionData *data) +{ + GladeSignalDef *signal_def; + GladeWidgetAdaptor *adaptor; + gchar *catalog = NULL; + gboolean is_gtk_adaptor = FALSE; + + signal_def = + glade_widget_adaptor_get_signal_def (glade_widget_get_adaptor (data->widget), + glade_signal_get_name (signal)); + + /* unknown signal... can it happen ? */ + if (!signal_def) + return; + + adaptor = glade_signal_def_get_adaptor (signal_def); + + /* Check if the signal comes from a GTK+ widget class */ + g_object_get (adaptor, "catalog", &catalog, NULL); + if (strcmp (catalog, "gtk+") == 0) + is_gtk_adaptor = TRUE; + g_free (catalog); + + if (is_gtk_adaptor && + !GLADE_SIGNAL_DEF_VERSION_CHECK (signal_def, data->major, data->minor)) + { + data->major = glade_signal_def_since_major (signal_def); + data->minor = glade_signal_def_since_minor (signal_def); + } +} + +static void +glade_project_introspect_gtk_version (GladeProject *project) +{ + GladeWidget *widget; + GList *list, *l; + gint target_major = 2, target_minor = 12; + + for (list = project->priv->objects; list; list = list->next) + { + gboolean is_gtk_adaptor = FALSE; + gchar *catalog = NULL; + VersionData data = { 0, }; + GList *signals; + + widget = glade_widget_get_from_gobject (list->data); + + /* Check if its a GTK+ widget class */ + g_object_get (glade_widget_get_adaptor (widget), "catalog", &catalog, NULL); + if (strcmp (catalog, "gtk+") == 0) + is_gtk_adaptor = TRUE; + g_free (catalog); + + /* Check widget class version */ + if (is_gtk_adaptor && + !GLADE_WIDGET_ADAPTOR_VERSION_CHECK (glade_widget_get_adaptor (widget), target_major, target_minor)) + { + target_major = GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MAJOR (glade_widget_get_adaptor (widget)); + target_minor = GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MINOR (glade_widget_get_adaptor (widget)); + } + + /* Check all properties */ + for (l = glade_widget_get_properties (widget); l; l = l->next) + { + GladeProperty *property = l->data; + GladePropertyDef *pdef = glade_property_get_def (property); + GladeWidgetAdaptor *prop_adaptor, *adaptor; + GParamSpec *pspec; + + /* Unset properties ofcourse dont count... */ + if (glade_property_original_default (property)) + continue; + + /* Check if this property originates from a GTK+ widget class */ + pspec = glade_property_def_get_pspec (pdef); + prop_adaptor = glade_property_def_get_adaptor (pdef); + adaptor = glade_widget_adaptor_from_pspec (prop_adaptor, pspec); + + catalog = NULL; + is_gtk_adaptor = FALSE; + g_object_get (adaptor, "catalog", &catalog, NULL); + if (strcmp (catalog, "gtk+") == 0) + is_gtk_adaptor = TRUE; + g_free (catalog); + + /* Check GTK+ property class versions */ + if (is_gtk_adaptor && + !GLADE_PROPERTY_DEF_VERSION_CHECK (pdef, target_major, target_minor)) + { + target_major = glade_property_def_since_major (pdef); + target_minor = glade_property_def_since_minor (pdef); + } + } + + /* Check all signal versions here */ + data.widget = widget; + data.major = target_major; + data.minor = target_minor; + + signals = glade_widget_get_signal_list (widget); + g_list_foreach (signals, (GFunc)glade_project_introspect_signal_versions, &data); + g_list_free (signals); + + if (target_major < data.major) + target_major = data.major; + + if (target_minor < data.minor) + target_minor = data.minor; + } + + glade_project_set_target_version (project, "gtk+", target_major, + target_minor); +} + + +static gint +glade_project_count_xml_objects (GladeProject *project, + GladeXmlNode *root, + gint count) +{ + GladeXmlNode *node; + + for (node = glade_xml_node_get_children (root); + node; node = glade_xml_node_next (node)) + { + if (glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || + glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE)) + count = glade_project_count_xml_objects (project, node, ++count); + else if (glade_xml_node_verify_silent (node, GLADE_XML_TAG_CHILD)) + count = glade_project_count_xml_objects (project, node, count); + } + return count; +} + +void +glade_project_cancel_load (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + + project->priv->load_cancel = TRUE; +} + +gboolean +glade_project_load_cancelled (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + return project->priv->load_cancel; +} + +void +glade_project_push_progress (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + + project->priv->progress_step++; + + g_signal_emit (project, glade_project_signals[LOAD_PROGRESS], 0, + project->priv->progress_full, project->priv->progress_step); +} + + +/* translators: refers to project name '%s' that targets gtk version '%d.%d' */ +#define PROJECT_TARGET_DIALOG_TITLE_FMT _("%s targets Gtk+ %d.%d") + +static void +glade_project_check_target_version (GladeProject *project) +{ + GladeProjectPrivate *priv; + GHashTable *unknown_classes; + gint unknown_objects; + gint major, minor; + GtkWidget *dialog; + GString *text = NULL; + GList *l; + gchar *project_name; + + glade_project_get_target_version (project, "gtk+", &major, &minor); + + /* Glade >= 3.10 only targets Gtk 3 */ + if (major >= 3) return; + + priv = project->priv; + unknown_classes = g_hash_table_new (g_str_hash, g_str_equal); + unknown_objects = 0; + + for (l = priv->objects; l; l = g_list_next (l)) + { + if (GLADE_IS_OBJECT_STUB (l->data)) + { + gchar *type; + g_object_get (l->data, "object-type", &type, NULL); + g_hash_table_insert (unknown_classes, type, NULL); + unknown_objects++; + } + } + + if (unknown_objects) + { + GList *classes = g_hash_table_get_keys (unknown_classes); + GString *missing_types = g_string_new (NULL); + text = g_string_new (NULL); + + for (l = classes; l; l = g_list_next (l)) + { + if (g_list_previous (l)) { + g_string_append_printf (missing_types, _(", %s"), (const gchar *)l->data); + } else + g_string_append (missing_types, l->data); + } + + g_list_free (classes); + + g_string_printf (text, + ngettext ("Especially because there is %d object that can not be built with type: %s", + "Especially because there are %d objects that can not be built with types: %s", unknown_objects), + unknown_objects, + missing_types->str); + + g_string_free (missing_types, TRUE); + } + + project_name = glade_project_get_name (project); + dialog = gtk_message_dialog_new (GTK_WINDOW (glade_app_get_window ()), + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, GTK_BUTTONS_OK, + PROJECT_TARGET_DIALOG_TITLE_FMT, + project_name, + major, minor); + g_free (project_name); + + gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), + _("But this version of Glade is for GTK+ 3 only.\n" + "Make sure you can run this project with Glade 3.8 with no deprecated widgets first.\n" + "%s"), (text) ? text->str : ""); + + gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + glade_project_set_target_version (project, "gtk+", 3, 0); + + g_hash_table_destroy (unknown_classes); + if (text) g_string_free (text, TRUE); +} + +static gchar * +glade_project_autosave_name (const gchar *path) +{ + gchar *basename, *dirname, *autoname; + gchar *autosave_name; + + basename = g_path_get_basename (path); + dirname = g_path_get_dirname (path); + + autoname = g_strdup_printf ("#%s#", basename); + autosave_name = g_build_filename (dirname, autoname, NULL); + + g_free (basename); + g_free (dirname); + g_free (autoname); + + return autosave_name; +} + +static gboolean +glade_project_load_internal (GladeProject *project) +{ + GladeProjectPrivate *priv = project->priv; + GladeXmlContext *context; + GladeXmlDoc *doc; + GladeXmlNode *root; + GladeXmlNode *node; + GladeWidget *widget; + gboolean has_gtk_dep = FALSE; + gchar *domain; + gint count; + gchar *autosave_path; + time_t mtime, autosave_mtime; + gchar *load_path = NULL; + + /* Check if an autosave is more recent then the specified file */ + autosave_path = glade_project_autosave_name (priv->path); + autosave_mtime = glade_util_get_file_mtime (autosave_path, NULL); + mtime = glade_util_get_file_mtime (priv->path, NULL); + + if (autosave_mtime > mtime) + { + gchar *display_name; + + display_name = glade_project_get_name (project); + + if (glade_util_ui_message (glade_app_get_window (), + GLADE_UI_YES_OR_NO, NULL, + _("An automatically saved version of `%s' is more recent.\n\n" + "Would you like to load the autosave version instead?"), + display_name)) + { + mtime = autosave_mtime; + load_path = g_strdup (autosave_path); + } + g_free (display_name); + } + + g_free (autosave_path); + + priv->selection = NULL; + priv->objects = NULL; + priv->loading = TRUE; + + _glade_xml_error_reset_last (); + + /* get the context & root node of the catalog file */ + if (!(context = + glade_xml_context_new_from_path (load_path ? load_path : priv->path, NULL, NULL))) + { + gchar *message = _glade_xml_error_get_last_message (); + + if (message) + { + gchar *escaped = g_markup_escape_text (message, -1); + glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, "%s", escaped); + g_free (escaped); + g_free (message); + } + else + glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, + "Couldn't open glade file [%s].", + load_path ? load_path : priv->path); + + g_free (load_path); + priv->loading = FALSE; + return FALSE; + } + + priv->mtime = mtime; + + doc = glade_xml_context_get_doc (context); + root = glade_xml_doc_get_root (doc); + + if (!glade_xml_node_verify_silent (root, GLADE_XML_TAG_PROJECT)) + { + if (glade_xml_node_verify_silent (root, "glade-interface")) + glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, + "This version of Glade does not support old libglade files.\n" + "Please use Glade 3.8 for GTK 2 files."); + else + glade_util_ui_message (glade_app_get_window (), GLADE_UI_ERROR, NULL, + "Couldn't recognize GtkBuilder xml.\nskipping %s", + load_path ? load_path : priv->path); + glade_xml_context_free (context); + g_free (load_path); + priv->loading = FALSE; + return FALSE; + } + + /* Emit "parse-began" signal */ + g_signal_emit (project, glade_project_signals[PARSE_BEGAN], 0); + + if ((domain = glade_xml_get_property_string (root, GLADE_TAG_DOMAIN))) + { + glade_project_set_translation_domain (project, domain); + g_free (domain); + } + + glade_project_read_comments (project, root); + + /* Read requires, and do not abort load if there are missing catalog since + * GladeObjectStub is created to keep the original xml for unknown object classes + */ + glade_project_read_requires (project, root, load_path ? load_path : priv->path, &has_gtk_dep); + + /* Read the rest of properties saved as comments */ + glade_project_read_comment_properties (project, root); + + /* Launch a dialog if it's going to take enough time to be + * worth showing at all */ + count = glade_project_count_xml_objects (project, root, 0); + priv->progress_full = count; + priv->progress_step = 0; + + for (node = glade_xml_node_get_children (root); + node; node = glade_xml_node_next (node)) + { + /* Skip "requires" tags */ + if (!(glade_xml_node_verify_silent (node, GLADE_XML_TAG_WIDGET) || + glade_xml_node_verify_silent (node, GLADE_XML_TAG_TEMPLATE))) + continue; + + if ((widget = glade_widget_read (project, NULL, node, NULL)) != NULL) + glade_project_add_object (project, glade_widget_get_object (widget)); + + if (priv->load_cancel) + break; + } + + /* Finished with the xml context */ + glade_xml_context_free (context); + + if (priv->load_cancel) + { + priv->loading = FALSE; + g_free (load_path); + return FALSE; + } + + if (!has_gtk_dep) + glade_project_introspect_gtk_version (project); + + if (glade_util_file_is_writeable (priv->path) == FALSE) + glade_project_set_readonly (project, TRUE); + + /* Now we have to loop over all the object properties + * and fix'em all ('cause they probably weren't found) + */ + glade_project_fix_object_props (project); + + glade_project_fix_template (project); + + /* Emit "parse-finished" signal */ + g_signal_emit (project, glade_project_signals[PARSE_FINISHED], 0); + + /* Reset project status here too so that you get a clean + * slate after calling glade_project_open(). + */ + priv->modified = FALSE; + priv->loading = FALSE; + + /* Update ui with versioning info + */ + glade_project_verify_project_for_ui (project); + + glade_project_check_target_version (project); + + return TRUE; + +} + +gboolean +glade_project_load_from_file (GladeProject *project, const gchar *path) +{ + g_return_val_if_fail (path != NULL, FALSE); + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + project->priv->path = glade_util_canonical_path (path); + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_PATH]); + + return glade_project_load_internal (project); +} + +/** + * glade_project_load: + * @path: the path of the project to load + * + * Opens a project at the given path. + * + * Returns: (transfer full) (nullable): a new #GladeProject for the opened project on success, + * %NULL on failure + */ +GladeProject * +glade_project_load (const gchar *path) +{ + GladeProject *project; + + g_return_val_if_fail (path != NULL, NULL); + + project = g_object_new (GLADE_TYPE_PROJECT, NULL); + + project->priv->path = glade_util_canonical_path (path); + + if (glade_project_load_internal (project)) + return project; + + g_object_unref (project); + return NULL; +} + +/******************************************************************* + Writing project code here + *******************************************************************/ +#define GLADE_PROJECT_COMMENT " "GLADE_XML_COMMENT" "PACKAGE_VERSION" " + +static void +glade_project_write_comment_property (GladeProject *project, + GladeXmlContext *context, + GladeXmlNode *root, + const gchar *property, + gchar *value) +{ + gchar *comment, *escaped; + GladeXmlNode *path_node; + + if (!value || *value == '\0') + return; + + /* The string "--" (double hyphen) is not allowed in xml comments, so we replace + * the regular HYPHEN with a NON-BREAKING HYPHEN which look the same but have + * a different encoding. + */ + escaped = _glade_util_strreplace (g_strescape (value, "‑"), TRUE, "--", "\\‑\\‑"); + + comment = g_strconcat (" ", property, " ", escaped, " ", NULL); + path_node = glade_xml_node_new_comment (context, comment); + glade_xml_node_append_child (root, path_node); + + g_free (escaped); + g_free (comment); +} + +static void +glade_project_write_required_libs (GladeProject *project, + GladeXmlContext *context, + GladeXmlNode *root) +{ + gboolean supports_require_tag; + GladeXmlNode *req_node; + GList *required, *list; + gint major, minor; + + glade_project_get_target_version (project, "gtk+", &major, &minor); + supports_require_tag = GLADE_GTKBUILDER_HAS_VERSIONING (major, minor); + + if ((required = glade_project_required_libs (project)) != NULL) + { + gchar version[16]; + + for (list = required; list; list = list->next) + { + gchar *library = list->data; + + glade_project_get_target_version (project, library, &major, &minor); + + g_snprintf (version, sizeof (version), "%d.%d", major, minor); + + /* Write the standard requires tag */ + if (supports_require_tag) + { + req_node = glade_xml_node_new (context, GLADE_XML_TAG_REQUIRES); + glade_xml_node_set_property_string (req_node, + GLADE_XML_TAG_LIB, + library); + glade_xml_node_set_property_string (req_node, + GLADE_XML_TAG_VERSION, + version); + glade_xml_node_append_child (root, req_node); + } + else + { + gchar *value = g_strconcat (library, " ", version, NULL); + glade_project_write_comment_property (project, context, root, + "interface-requires", + value); + g_free (value); + } + } + g_list_free_full (required, g_free); + } +} + +static void +glade_project_write_resource_path (GladeProject *project, + GladeXmlContext *context, + GladeXmlNode *root) +{ + glade_project_write_comment_property (project, context, root, + "interface-local-resource-path", + project->priv->resource_path); +} + +static void +glade_project_write_css_provider_path (GladeProject *project, + GladeXmlContext *context, + GladeXmlNode *root) +{ + GladeProjectPrivate *priv = project->priv; + gchar *dirname; + + if (priv->css_provider_path && priv->path && + (dirname = g_path_get_dirname (priv->path))) + { + GFile *project_path = g_file_new_for_path (dirname); + GFile *file_path = g_file_new_for_path (priv->css_provider_path); + gchar *css_provider_path; + + css_provider_path = g_file_get_relative_path (project_path, file_path); + + if (css_provider_path) + glade_project_write_comment_property (project, context, root, + "interface-css-provider-path", + css_provider_path); + else + g_warning ("g_file_get_relative_path () return NULL"); + + g_object_unref (project_path); + g_object_unref (file_path); + g_free (css_provider_path); + g_free (dirname); + } +} + +static void +glade_project_write_license_data (GladeProject *project, + GladeXmlContext *context, + GladeXmlNode *root) +{ + g_autofree gchar *license = NULL, *name = NULL, *description = NULL, *copyright = NULL, *authors = NULL; + + if (!project->priv->prefs_dialog) + return; + + _glade_project_properties_get_license_data (GLADE_PROJECT_PROPERTIES (project->priv->prefs_dialog), + &license, + &name, + &description, + ©right, + &authors); + + glade_project_write_comment_property (project, context, root, + "interface-license-type", + license); + glade_project_write_comment_property (project, context, root, + "interface-name", + name); + glade_project_write_comment_property (project, context, root, + "interface-description", + description); + glade_project_write_comment_property (project, context, root, + "interface-copyright", + copyright); + glade_project_write_comment_property (project, context, root, + "interface-authors", + authors); +} + +static gint +glade_widgets_name_cmp (gconstpointer ga, gconstpointer gb) +{ + return g_strcmp0 (glade_widget_get_name ((gpointer)ga), + glade_widget_get_name ((gpointer)gb)); +} + +static gint +glade_node_edge_name_cmp (gconstpointer ga, gconstpointer gb) +{ + _NodeEdge *na = (gpointer)ga, *nb = (gpointer)gb; + return g_strcmp0 (glade_widget_get_name (nb->successor), + glade_widget_get_name (na->successor)); +} + +static inline gboolean +glade_project_widget_hard_depends (GladeWidget *widget, GladeWidget *another) +{ + GList *l; + + for (l = _glade_widget_peek_prop_refs (another); l; l = g_list_next (l)) + { + GladePropertyDef *def; + + /* If one of the properties that reference @another is + * owned by @widget then @widget depends on @another + */ + if (glade_property_get_widget (l->data) == widget && + (def = glade_property_get_def (l->data)) && + glade_property_def_get_construct_only (def)) + return TRUE; + } + + return FALSE; +} + +static GList * +glade_project_get_graph_deps (GladeProject *project) +{ + GladeProjectPrivate *priv = project->priv; + GList *l, *edges = NULL; + + /* Create edges of the directed graph */ + for (l = priv->objects; l; l = g_list_next (l)) + { + GladeWidget *predecessor = glade_widget_get_from_gobject (l->data); + GladeWidget *predecessor_top; + GList *ll; + + predecessor_top = glade_widget_get_toplevel (predecessor); + + /* Adds dependencies based on properties refs */ + for (ll = _glade_widget_peek_prop_refs (predecessor); ll; ll = g_list_next (ll)) + { + GladeWidget *successor = glade_property_get_widget (ll->data); + GladeWidget *successor_top; + + /* Ignore widgets that are not part of this project. (ie removed ones) */ + if (glade_widget_get_project (successor) != project) + continue; + + successor_top = glade_widget_get_toplevel (successor); + + /* Ignore objects within the same toplevel */ + if (predecessor_top != successor_top) + edges = _node_edge_prepend (edges, predecessor_top, successor_top); + } + } + + return edges; +} + +static GList * +glade_project_get_nodes_from_edges (GladeProject *project, GList *edges) +{ + GList *l, *hard_edges = NULL; + GList *cycles = NULL; + + /* Collect widgets with circular dependencies */ + for (l = edges; l; l = g_list_next (l)) + { + _NodeEdge *edge = l->data; + + if (glade_widget_get_parent (edge->successor) == edge->predecessor || + glade_project_widget_hard_depends (edge->predecessor, edge->successor)) + hard_edges = _node_edge_prepend (hard_edges, edge->predecessor, edge->successor); + + /* Just toplevels */ + if (glade_widget_get_parent (edge->successor)) + continue; + + if (!g_list_find (cycles, edge->successor)) + cycles = g_list_prepend (cycles, edge->successor); + } + + /* Sort them alphabetically */ + cycles = g_list_sort (cycles, glade_widgets_name_cmp); + + if (!hard_edges) + return cycles; + + /* Sort them by hard deps */ + cycles = _glade_tsort (&cycles, &hard_edges); + + if (hard_edges) + { + GList *l, *hard_cycles = NULL; + + /* Collect widgets with hard circular dependencies */ + for (l = hard_edges; l; l = g_list_next (l)) + { + _NodeEdge *edge = l->data; + + /* Just toplevels */ + if (glade_widget_get_parent (edge->successor)) + continue; + + if (!g_list_find (hard_cycles, edge->successor)) + hard_cycles = g_list_prepend (hard_cycles, edge->successor); + } + + /* And append to the end of the cycles list */ + cycles = g_list_concat (cycles, g_list_sort (hard_cycles, glade_widgets_name_cmp)); + + /* Opps! there is at least one hard circular dependency, + * GtkBuilder will fail to set one of the properties that create the cycle + */ + + _node_edge_list_free (hard_edges); + } + + return cycles; +} + +static GList * +glade_project_add_hardcoded_dependencies (GList *edges, GladeProject *project) +{ + GList *l, *toplevels = project->priv->tree; + + /* Iterate over every toplevel */ + for (l = toplevels; l; l = g_list_next (l)) + { + GObject *predecessor = l->data; + + /* Looking for a GtkIconFactory */ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + if (GTK_IS_ICON_FACTORY (predecessor)) + { + GladeWidget *predecessor_top = glade_widget_get_from_gobject (predecessor); + GList *ll; + + /* add dependency on the GtkIconFactory on every toplevel */ + for (ll = toplevels; ll; ll = g_list_next (ll)) + { + GObject *successor = ll->data; + + /* except for GtkIconFactory */ + if (!GTK_IS_ICON_FACTORY (successor)) + edges = _node_edge_prepend (edges, predecessor_top, + glade_widget_get_from_gobject (successor)); + } + } +G_GNUC_END_IGNORE_DEPRECATIONS + } + + return edges; +} + +static GList * +glade_project_get_ordered_toplevels (GladeProject *project) +{ + GladeProjectPrivate *priv = project->priv; + GList *l, *sorted_tree, *tree = NULL; + GList *edges; + + /* Create list of toplevels GladeWidgets */ + for (l = priv->tree; l; l = g_list_next (l)) + tree = g_list_prepend (tree, glade_widget_get_from_gobject (l->data)); + + /* Get dependency graph */ + edges = glade_project_get_graph_deps (project); + + /* Added hardcoded dependencies */ + edges = glade_project_add_hardcoded_dependencies (edges, project); + + /* Sort toplevels alphabetically */ + tree = g_list_sort (tree, glade_widgets_name_cmp); + + /* Sort dep graph alphabetically using successor name. + * _glade_tsort() is a stable sort algorithm so, output of nodes without + * dependency depends on the input order + */ + edges = g_list_sort (edges, glade_node_edge_name_cmp); + + /* Sort tree */ + sorted_tree = _glade_tsort (&tree, &edges); + + if (edges) + { + GList *cycles = glade_project_get_nodes_from_edges (project, edges); + + /* And append to the end of the sorted list */ + sorted_tree = g_list_concat (sorted_tree, cycles); + + _node_edge_list_free (edges); + } + + /* No need to free tree as tsort will consume the list */ + return sorted_tree; +} + +static inline void +glade_project_write_comments (GladeProject *project, + GladeXmlDoc *doc, + GladeXmlNode *root) +{ + GladeProjectPrivate *priv = project->priv; + GladeXmlNode *comment_node, *node; + GList *l; + + if (priv->license) + { + /* Replace regular HYPHEN with NON-BREAKING HYPHEN */ + gchar *license = _glade_util_strreplace (priv->license, FALSE, "--", "‑‑"); + gchar *comment = g_strdup_printf (GLADE_PROJECT_COMMENT"\n\n%s\n\n", license); + g_free (license); + comment_node = glade_xml_doc_new_comment (doc, comment); + g_free (comment); + } + else + comment_node = glade_xml_doc_new_comment (doc, GLADE_PROJECT_COMMENT); + + comment_node = glade_xml_node_add_prev_sibling (root, comment_node); + + for (l = priv->comments; l; l = g_list_next (l)) + { + node = glade_xml_doc_new_comment (doc, l->data); + comment_node = glade_xml_node_add_next_sibling (comment_node, node); + } +} + +static GladeXmlContext * +glade_project_write (GladeProject *project) +{ + GladeProjectPrivate *priv = project->priv; + GladeXmlContext *context; + GladeXmlDoc *doc; + GladeXmlNode *root; + GList *list; + GList *toplevels; + + doc = glade_xml_doc_new (); + context = glade_xml_context_new (doc, NULL); + root = glade_xml_node_new (context, GLADE_XML_TAG_PROJECT); + glade_xml_doc_set_root (doc, root); + + if (priv->translation_domain) + glade_xml_node_set_property_string (root, GLADE_TAG_DOMAIN, priv->translation_domain); + + glade_project_write_comments (project, doc, root); + + glade_project_write_required_libs (project, context, root); + + glade_project_write_resource_path (project, context, root); + + glade_project_write_css_provider_path (project, context, root); + + glade_project_write_license_data (project, context, root); + + /* Get sorted toplevels */ + toplevels = glade_project_get_ordered_toplevels (project); + + for (list = toplevels; list; list = g_list_next (list)) + { + GladeWidget *widget = list->data; + + /* + * Append toplevel widgets. Each widget then takes + * care of appending its children. + */ + if (!glade_widget_get_parent (widget)) + glade_widget_write (widget, context, root); + else + g_warning ("Tried to save a non toplevel object '%s' at xml root", + glade_widget_get_name (widget)); + } + + g_list_free (toplevels); + + return context; +} + +/** + * glade_project_backup: + * @project: a #GladeProject + * @path: location to save glade file + * @error: an error from the G_FILE_ERROR domain. + * + * Backup the last file which @project has saved to + * or was loaded from. + * + * If @path is not the same as the current project + * path, then the current project path will be + * backed up under the new location. + * + * If this the first save, and no persisted file + * exists, then %TRUE is returned and no backup is made. + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +glade_project_backup (GladeProject *project, const gchar *path, GError **error) +{ + gchar *canonical_path; + gchar *destination_path; + gchar *content = NULL; + gsize length = 0; + gboolean success; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + if (project->priv->path == NULL) + return TRUE; + + canonical_path = glade_util_canonical_path (path); + destination_path = g_strconcat (canonical_path, "~", NULL); + g_free (canonical_path); + + success = g_file_get_contents (project->priv->path, &content, &length, error); + if (success) + { + success = g_file_set_contents (destination_path, content, length, error); + g_free (content); + } + + g_free (destination_path); + + return success; +} + +/** + * glade_project_autosave: + * @project: a #GladeProject + * @error: an error from the G_FILE_ERROR domain. + * + * Saves an autosave snapshot of @project to it's currently set path + * + * If the project was never saved, nothing is done and %TRUE is returned. + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +glade_project_autosave (GladeProject *project, GError **error) +{ + + GladeXmlContext *context; + GladeXmlDoc *doc; + gchar *autosave_path; + gint ret; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + if (project->priv->path == NULL) + return TRUE; + + autosave_path = glade_project_autosave_name (project->priv->path); + + context = glade_project_write (project); + doc = glade_xml_context_get_doc (context); + ret = glade_xml_doc_save (doc, autosave_path); + glade_xml_context_free (context); + + g_free (autosave_path); + + return ret > 0; +} + +static inline void +update_project_resource_path (GladeProject *project, gchar *path) +{ + GFile *new_resource_path; + GList *l; + + new_resource_path = g_file_new_for_path (path); + + for (l = project->priv->objects; l; l = l->next) + { + GladeWidget *widget = glade_widget_get_from_gobject (l->data); + GList *list; + + for (list = glade_widget_get_properties (widget); list; list = list->next) + { + GladeProperty *property = list->data; + GladePropertyDef *def = glade_property_get_def (property); + GParamSpec *pspec = glade_property_def_get_pspec (def); + + if (pspec->value_type == GDK_TYPE_PIXBUF) + { + const gchar *filename; + GObject *pixbuf; + + glade_property_get (property, &pixbuf); + if (pixbuf == NULL) + continue; + + filename = g_object_get_data (pixbuf, "GladeFileName"); + + if (!g_str_has_prefix (filename, "resource:///")) + { + g_autoptr(GFile) fullpath_file = NULL; + g_autofree gchar *fullpath = NULL; + gchar *relpath; + + fullpath = glade_project_resource_fullpath (project, filename); + fullpath_file = g_file_new_for_path (fullpath); + relpath = _glade_util_file_get_relative_path (new_resource_path, + fullpath_file); + g_object_set_data_full (pixbuf, "GladeFileName", relpath, g_free); + } + } + } + } + + g_object_unref (new_resource_path); +} + +static inline void +sync_project_resource_path (GladeProject *project) +{ + GList *l; + + for (l = glade_project_selection_get (project); l; l = l->next) + { + GladeWidget *widget = glade_widget_get_from_gobject (l->data); + GList *list; + + for (list = glade_widget_get_properties (widget); list; list = list->next) + { + GladeProperty *property = list->data; + GladePropertyDef *def = glade_property_get_def (property); + GParamSpec *pspec = glade_property_def_get_pspec (def); + + if (pspec->value_type == GDK_TYPE_PIXBUF) + { + const gchar *filename; + GObject *pixbuf; + GValue *value; + + glade_property_get (property, &pixbuf); + if (pixbuf == NULL) + continue; + + filename = g_object_get_data (pixbuf, "GladeFileName"); + value = glade_property_def_make_gvalue_from_string (def, + filename, + project); + glade_property_set_value (property, value); + g_value_unset (value); + g_free (value); + } + } + } +} + +/** + * glade_project_save_verify: + * @project: a #GladeProject + * @path: location to save glade file + * @flags: the #GladeVerifyFlags to warn about + * @error: an error from the %G_FILE_ERROR domain. + * + * Saves @project to the given path. + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +glade_project_save_verify (GladeProject *project, + const gchar *path, + GladeVerifyFlags flags, + GError **error) +{ + GladeXmlContext *context; + GladeXmlDoc *doc; + gchar *canonical_path; + gint ret; + gchar *autosave_path; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + if (glade_project_is_loading (project)) + return FALSE; + + if (!glade_project_verify (project, TRUE, flags)) + return FALSE; + + /* Delete any autosaves at this point, if they exist */ + if (project->priv->path) + { + autosave_path = glade_project_autosave_name (project->priv->path); + g_unlink (autosave_path); + g_free (autosave_path); + } + + if (!project->priv->resource_path) + { + /* Fix pixbuf paths: Since there is no resource_path, images are relative + * to path or CWD so they need to be updated to be relative to @path + */ + gchar *dirname = g_path_get_dirname (path); + update_project_resource_path (project, dirname); + g_free (dirname); + } + + /* Save the project */ + context = glade_project_write (project); + doc = glade_xml_context_get_doc (context); + ret = glade_xml_doc_save (doc, path); + glade_xml_context_free (context); + + canonical_path = glade_util_canonical_path (path); + g_assert (canonical_path); + + if (project->priv->path == NULL || + strcmp (canonical_path, project->priv->path)) + { + project->priv->path = (g_free (project->priv->path), + g_strdup (canonical_path)); + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_PATH]); + + /* Sync selected objects pixbuf properties */ + sync_project_resource_path (project); + } + + glade_project_set_readonly (project, + !glade_util_file_is_writeable (project->priv-> + path)); + + project->priv->mtime = glade_util_get_file_mtime (project->priv->path, NULL); + + glade_project_set_modified (project, FALSE); + + if (project->priv->unsaved_number > 0) + { + glade_id_allocator_release (get_unsaved_number_allocator (), + project->priv->unsaved_number); + project->priv->unsaved_number = 0; + } + + g_free (canonical_path); + + return ret > 0; +} + +/** + * glade_project_save: + * @project: a #GladeProject + * @path: location to save glade file + * @error: an error from the %G_FILE_ERROR domain. + * + * Saves @project to the given path. + * + * Returns: %TRUE on success, %FALSE on failure + */ +gboolean +glade_project_save (GladeProject *project, const gchar *path, GError **error) +{ + return glade_project_save_verify (project, path, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_UNRECOGNIZED, + error); +} + +/** + * glade_project_preview: + * @project: a #GladeProject + * @gwidget: a #GladeWidget + * + * Creates and displays a preview window holding a snapshot of @gwidget's + * toplevel window in @project. Note that the preview window is only a snapshot + * of the current state of the project, there is no limit on how many preview + * snapshots can be taken. + */ +void +glade_project_preview (GladeProject *project, GladeWidget *gwidget) +{ + GladeXmlContext *context; + gchar *text, *pidstr; + GladePreview *preview = NULL; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + project->priv->writing_preview = TRUE; + context = glade_project_write (project); + project->priv->writing_preview = FALSE; + + text = glade_xml_dump_from_context (context); + glade_xml_context_free (context); + + gwidget = glade_widget_get_toplevel (gwidget); + if (!GTK_IS_WIDGET (glade_widget_get_object (gwidget))) + return; + + if ((pidstr = g_object_get_data (G_OBJECT (gwidget), "preview")) != NULL) + preview = g_hash_table_lookup (project->priv->previews, pidstr); + + if (!preview) + { + /* If the previewer program is somehow missing, this can return NULL */ + preview = glade_preview_launch (gwidget, text); + g_return_if_fail (GLADE_IS_PREVIEW (preview)); + + /* Leave project data on the preview */ + g_object_set_data (G_OBJECT (preview), "project", project); + + /* Leave preview data on the widget */ + g_object_set_data_full (G_OBJECT (gwidget), + "preview", + glade_preview_get_pid_as_str (preview), + g_free); + + g_signal_connect (preview, "exits", + G_CALLBACK (glade_project_preview_exits), + project); + + /* Add preview to list of previews */ + g_hash_table_insert (project->priv->previews, + glade_preview_get_pid_as_str (preview), + preview); + } + else + { + glade_preview_update (preview, text); + } + + g_free (text); +} + +gboolean +glade_project_writing_preview (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + return project->priv->writing_preview; +} + +/******************************************************************* + Verify code here (versioning, incompatibility checks) + *******************************************************************/ + +/* Defined here for pretty translator comments (bug in intl tools, for some reason + * you can only comment about the line directly following, forcing you to write + * ugly messy code with comments in line breaks inside function calls). + */ + +/* translators: refers to a widget in toolkit version '%s %d.%d' and a project targeting toolkit version '%s %d.%d' */ +#define WIDGET_VERSION_CONFLICT_MSGFMT _("This widget was introduced in %s %d.%d " \ + "while project targets %s %d.%d") + +/* translators: refers to a widget '[%s]' introduced in toolkit version '%s %d.%d' */ +#define WIDGET_VERSION_CONFLICT_FMT _("[%s]\n\tObject class '%s' was introduced in %s %d.%d\n") + +#define WIDGET_DEPRECATED_MSG _("This widget is deprecated") + +/* translators: refers to a widget '[%s]' loaded from toolkit version '%s %d.%d' */ +#define WIDGET_DEPRECATED_FMT _("[%s]\n\tObject class '%s' from %s %d.%d is deprecated\n") + + +/* translators: refers to a property in toolkit version '%s %d.%d' + * and a project targeting toolkit version '%s %d.%d' */ +#define PROP_VERSION_CONFLICT_MSGFMT _("This property was introduced in %s %d.%d " \ + "while project targets %s %d.%d") + +/* translators: refers to a property '%s' of widget '[%s]' in toolkit version '%s %d.%d' */ +#define PROP_VERSION_CONFLICT_FMT _("[%s]\n\tProperty '%s' of object class '%s' " \ + "was introduced in %s %d.%d\n") + +/* translators: refers to a property '%s' of widget '[%s]' in toolkit version '%s %d.%d' */ +#define PACK_PROP_VERSION_CONFLICT_FMT _("[%s]\n\tPacking property '%s' of object class '%s' " \ + "was introduced in %s %d.%d\n") + +#define PROP_DEPRECATED_MSG _("This property is deprecated") + +/* translators: refers to a property '%s' of widget '[%s]' */ +#define PROP_DEPRECATED_FMT _("[%s]\n\tProperty '%s' of object class '%s' is deprecated\n") + +/* translators: refers to a signal in toolkit version '%s %d.%d' + * and a project targeting toolkit version '%s %d.%d' */ +#define SIGNAL_VERSION_CONFLICT_MSGFMT _("This signal was introduced in %s %d.%d " \ + "while project targets %s %d.%d") + +/* translators: refers to a signal '%s' of widget '[%s]' in toolkit version '%s %d.%d' */ +#define SIGNAL_VERSION_CONFLICT_FMT _("[%s]\n\tSignal '%s' of object class '%s' " \ + "was introduced in %s %d.%d\n") + +#define SIGNAL_DEPRECATED_MSG _("This signal is deprecated") + +/* translators: refers to a signal '%s' of widget '[%s]' */ +#define SIGNAL_DEPRECATED_FMT _("[%s]\n\tSignal '%s' of object class '%s' is deprecated\n") + + +static void +glade_project_verify_property_internal (GladeProject *project, + GladeProperty *property, + const gchar *path_name, + GString *string, + gboolean forwidget, + GladeVerifyFlags flags) +{ + GladeWidgetAdaptor *adaptor, *prop_adaptor; + GladePropertyDef *pdef; + GParamSpec *pspec; + gint target_major, target_minor; + gchar *catalog, *tooltip; + + /* For verification lists, we're only interested in verifying the 'used' state of properties. + * + * For the UI on the other hand, we want to show warnings on unset properties until they + * are set. + */ + if (!forwidget && (glade_property_get_state (property) & GLADE_STATE_CHANGED) == 0) + return; + + pdef = glade_property_get_def (property); + pspec = glade_property_def_get_pspec (pdef); + prop_adaptor = glade_property_def_get_adaptor (pdef); + adaptor = glade_widget_adaptor_from_pspec (prop_adaptor, pspec); + + g_object_get (adaptor, "catalog", &catalog, NULL); + glade_project_target_version_for_adaptor (project, adaptor, + &target_major, &target_minor); + + if ((flags & GLADE_VERIFY_VERSIONS) != 0 && + !GLADE_PROPERTY_DEF_VERSION_CHECK (pdef, target_major, target_minor)) + { + GLADE_NOTE (VERIFY, g_print ("VERIFY: Property '%s' of adaptor %s not available in version %d.%d\n", + glade_property_def_id (pdef), + glade_widget_adaptor_get_name (adaptor), + target_major, target_minor)); + + if (forwidget) + { + tooltip = g_strdup_printf (PROP_VERSION_CONFLICT_MSGFMT, + catalog, + glade_property_def_since_major (pdef), + glade_property_def_since_minor (pdef), + catalog, target_major, target_minor); + + glade_property_set_support_warning (property, FALSE, tooltip); + g_free (tooltip); + } + else + g_string_append_printf (string, + glade_property_def_get_is_packing (pdef) ? + PACK_PROP_VERSION_CONFLICT_FMT : + PROP_VERSION_CONFLICT_FMT, + path_name, + glade_property_def_get_name (pdef), + glade_widget_adaptor_get_title (adaptor), + catalog, + glade_property_def_since_major (pdef), + glade_property_def_since_minor (pdef)); + } + else if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 && + GLADE_PROPERTY_DEF_DEPRECATED_SINCE_CHECK (pdef, target_major, target_minor)) + { + GLADE_NOTE (VERIFY, g_print ("VERIFY: Property '%s' of adaptor %s is deprecated since version %d.%d\n", + glade_property_def_id (pdef), + glade_widget_adaptor_get_name (adaptor), + glade_property_def_deprecated_since_major (pdef), + glade_property_def_deprecated_since_minor (pdef))); + + if (forwidget) + glade_property_set_support_warning (property, FALSE, PROP_DEPRECATED_MSG); + else + g_string_append_printf (string, + PROP_DEPRECATED_FMT, + path_name, + glade_property_def_get_name (pdef), + glade_widget_adaptor_get_title (adaptor)); + } + else if (forwidget) + glade_property_set_support_warning (property, FALSE, NULL); + + g_free (catalog); +} + +static void +glade_project_verify_properties_internal (GladeWidget *widget, + const gchar *path_name, + GString *string, + gboolean forwidget, + GladeVerifyFlags flags) +{ + GList *list; + GladeProperty *property; + + for (list = glade_widget_get_properties (widget); list; list = list->next) + { + property = list->data; + glade_project_verify_property_internal (glade_widget_get_project (widget), + property, path_name, + string, forwidget, flags); + } + + /* Sometimes widgets on the clipboard have packing props with no parent */ + if (glade_widget_get_parent (widget)) + { + for (list = glade_widget_get_packing_properties (widget); list; list = list->next) + { + property = list->data; + glade_project_verify_property_internal (glade_widget_get_project (widget), + property, path_name, string, forwidget, flags); + } + } +} + +static void +glade_project_verify_signal_internal (GladeWidget *widget, + GladeSignal *signal, + const gchar *path_name, + GString *string, + gboolean forwidget, + GladeVerifyFlags flags) +{ + GladeSignalDef *signal_def; + GladeWidgetAdaptor *adaptor; + gint target_major, target_minor; + gchar *catalog; + GladeProject *project; + + signal_def = + glade_widget_adaptor_get_signal_def (glade_widget_get_adaptor (widget), + glade_signal_get_name (signal)); + + if (!signal_def) + return; + + adaptor = glade_signal_def_get_adaptor (signal_def); + project = glade_widget_get_project (widget); + + if (!project) + return; + + g_object_get (adaptor, "catalog", &catalog, NULL); + glade_project_target_version_for_adaptor (project, adaptor, + &target_major, &target_minor); + + if ((flags & GLADE_VERIFY_VERSIONS) != 0 && + !GLADE_SIGNAL_DEF_VERSION_CHECK (signal_def, target_major, target_minor)) + { + GLADE_NOTE (VERIFY, g_print ("VERIFY: Signal '%s' of adaptor %s not available in version %d.%d\n", + glade_signal_get_name (signal), + glade_widget_adaptor_get_name (adaptor), + target_major, target_minor)); + + if (forwidget) + { + gchar *warning; + + warning = g_strdup_printf (SIGNAL_VERSION_CONFLICT_MSGFMT, + catalog, + glade_signal_def_since_major (signal_def), + glade_signal_def_since_minor (signal_def), + catalog, target_major, target_minor); + glade_signal_set_support_warning (signal, warning); + g_free (warning); + } + else + g_string_append_printf (string, + SIGNAL_VERSION_CONFLICT_FMT, + path_name, + glade_signal_get_name (signal), + glade_widget_adaptor_get_title (adaptor), + catalog, + glade_signal_def_since_major (signal_def), + glade_signal_def_since_minor (signal_def)); + } + else if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 && + GLADE_SIGNAL_DEF_DEPRECATED_SINCE_CHECK (signal_def, target_major, target_minor)) + { + GLADE_NOTE (VERIFY, g_print ("VERIFY: Signal '%s' of adaptor %s is deprecated since %d.%d\n", + glade_signal_get_name (signal), + glade_widget_adaptor_get_name (adaptor), + glade_signal_def_deprecated_since_major (signal_def), + glade_signal_def_deprecated_since_minor (signal_def))); + + if (forwidget) + glade_signal_set_support_warning (signal, SIGNAL_DEPRECATED_MSG); + else + g_string_append_printf (string, + SIGNAL_DEPRECATED_FMT, + path_name, + glade_signal_get_name (signal), + glade_widget_adaptor_get_title (adaptor)); + } + else if (forwidget) + glade_signal_set_support_warning (signal, NULL); + + g_free (catalog); +} + +void +glade_project_verify_property (GladeProperty *property) +{ + GladeWidget *widget; + GladeProject *project; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + + widget = glade_property_get_widget (property); + project = glade_widget_get_project (widget); + + if (project) + glade_project_verify_property_internal (project, property, NULL, NULL, TRUE, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED); +} + +void +glade_project_verify_signal (GladeWidget *widget, GladeSignal *signal) +{ + glade_project_verify_signal_internal (widget, signal, NULL, NULL, TRUE, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED); +} + +static void +glade_project_verify_signals (GladeWidget *widget, + const gchar *path_name, + GString *string, + gboolean forwidget, + GladeVerifyFlags flags) +{ + GladeSignal *signal; + GList *signals, *list; + + if ((signals = glade_widget_get_signal_list (widget)) != NULL) + { + for (list = signals; list; list = list->next) + { + signal = list->data; + glade_project_verify_signal_internal (widget, signal, path_name, + string, forwidget, flags); + } + g_list_free (signals); + } +} + + +/** + * glade_project_verify_properties: + * @widget: A #GladeWidget + * + * Synchronizes @widget with user visible information + * about version compatibility and notifies the UI + * it should update. + */ +static void +glade_project_verify_properties (GladeWidget *widget) +{ + GladeProject *project; + + g_return_if_fail (GLADE_IS_WIDGET (widget)); + + project = glade_widget_get_project (widget); + if (!project || project->priv->loading) + return; + + glade_project_verify_properties_internal (widget, NULL, NULL, TRUE, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED); + glade_project_verify_signals (widget, NULL, NULL, TRUE, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED); + + glade_widget_support_changed (widget); +} + +static gboolean +glade_project_verify_dialog (GladeProject *project, GString *string) +{ + GtkWidget *swindow; + GtkWidget *textview; + GtkWidget *expander; + GtkTextBuffer *buffer; + GtkTextIter iter; + gchar *name; + gboolean ret; + + swindow = gtk_scrolled_window_new (NULL, NULL); + textview = gtk_text_view_new (); + buffer = gtk_text_view_get_buffer (GTK_TEXT_VIEW (textview)); + expander = gtk_expander_new (_("Details")); + + gtk_text_buffer_get_start_iter (buffer, &iter); + gtk_text_buffer_insert_markup (buffer, &iter, string->str, -1); + gtk_widget_set_vexpand (swindow, TRUE); + + gtk_container_add (GTK_CONTAINER (swindow), textview); + gtk_container_add (GTK_CONTAINER (expander), swindow); + gtk_widget_show_all (expander); + + gtk_widget_set_size_request (swindow, 800, -1); + + name = glade_project_get_name (project); + ret = glade_util_ui_message (glade_app_get_window (), + GLADE_UI_YES_OR_NO, + expander, + _("Project \"%s\" has errors. Save anyway?"), + name); + g_free (name); + + return ret; +} + + +gboolean +glade_project_verify (GladeProject *project, + gboolean saving, + GladeVerifyFlags flags) +{ + GladeProjectPrivate *priv = project->priv; + GString *string = g_string_new (NULL); + GList *list; + gboolean ret = TRUE; + + GLADE_NOTE (VERIFY, g_print ("VERIFY: glade_project_verify() start\n")); + + if (project->priv->template) + { + gint major, minor; + glade_project_get_target_version (project, "gtk+", &major, &minor); + + if (major == 3 && minor < 10) + g_string_append_printf (string, _("Object %s is a class template but this is not supported in gtk+ %d.%d\n"), + glade_widget_get_name (project->priv->template), + major, minor); + } + + for (list = project->priv->objects; list; list = list->next) + { + GladeWidget *widget = glade_widget_get_from_gobject (list->data); + + if ((flags & GLADE_VERIFY_UNRECOGNIZED) != 0 && + GLADE_IS_OBJECT_STUB (list->data)) + { + gchar *type; + g_object_get (list->data, "object-type", &type, NULL); + + g_string_append_printf (string, _("Object %s has unrecognized type %s\n"), + glade_widget_get_name (widget), type); + g_free (type); + } + else + { + gchar *path_name = glade_widget_generate_path_name (widget); + + glade_project_verify_adaptor (project, glade_widget_get_adaptor (widget), + path_name, string, flags, FALSE, NULL); + glade_project_verify_properties_internal (widget, path_name, string, FALSE, flags); + glade_project_verify_signals (widget, path_name, string, FALSE, flags); + + g_free (path_name); + } + } + + /* Update project warnings */ + _glade_project_properties_set_warnings (GLADE_PROJECT_PROPERTIES (priv->prefs_dialog), + string->str); + + if (string->len > 0) + { + if (saving) + ret = glade_project_verify_dialog (project, string); + else + ret = FALSE; + } + + g_string_free (string, TRUE); + + GLADE_NOTE (VERIFY, g_print ("VERIFY: glade_project_verify() end\n")); + + return ret; +} + +static void +glade_project_target_version_for_adaptor (GladeProject *project, + GladeWidgetAdaptor *adaptor, + gint *major, + gint *minor) +{ + gchar *catalog = NULL; + + g_object_get (adaptor, "catalog", &catalog, NULL); + glade_project_get_target_version (project, catalog, major, minor); + g_free (catalog); +} + +static void +glade_project_verify_adaptor (GladeProject *project, + GladeWidgetAdaptor *adaptor, + const gchar *path_name, + GString *string, + GladeVerifyFlags flags, + gboolean forwidget, + GladeSupportMask *mask) +{ + GladeSupportMask support_mask = GLADE_SUPPORT_OK; + GladeWidgetAdaptor *adaptor_iter; + gint target_major, target_minor; + gchar *catalog = NULL; + + for (adaptor_iter = adaptor; adaptor_iter && support_mask == GLADE_SUPPORT_OK; + adaptor_iter = glade_widget_adaptor_get_parent_adaptor (adaptor_iter)) + { + + g_object_get (adaptor_iter, "catalog", &catalog, NULL); + glade_project_target_version_for_adaptor (project, adaptor_iter, + &target_major, &target_minor); + + /* Only one versioning message (builder or otherwise)... + */ + if ((flags & GLADE_VERIFY_VERSIONS) != 0 && + !GLADE_WIDGET_ADAPTOR_VERSION_CHECK (adaptor_iter, target_major, target_minor)) + { + GLADE_NOTE (VERIFY, g_print ("VERIFY: Adaptor '%s' not available in version %d.%d\n", + glade_widget_adaptor_get_name (adaptor_iter), + target_major, target_minor)); + + if (forwidget) + g_string_append_printf (string, + WIDGET_VERSION_CONFLICT_MSGFMT, + catalog, + GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MAJOR (adaptor_iter), + GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MINOR (adaptor_iter), + catalog, target_major, target_minor); + else + g_string_append_printf (string, + WIDGET_VERSION_CONFLICT_FMT, + path_name, + glade_widget_adaptor_get_title (adaptor_iter), + catalog, + GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MAJOR (adaptor_iter), + GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MINOR (adaptor_iter)); + + support_mask |= GLADE_SUPPORT_MISMATCH; + } + + if ((flags & GLADE_VERIFY_DEPRECATIONS) != 0 && + (GLADE_WIDGET_ADAPTOR_DEPRECATED (adaptor_iter) || + GLADE_WIDGET_ADAPTOR_DEPRECATED_SINCE_CHECK (adaptor_iter, target_major, target_minor))) + { + GLADE_NOTE (VERIFY, g_print ("VERIFY: Adaptor '%s' is deprecated\n", + glade_widget_adaptor_get_name (adaptor_iter))); + + if (forwidget) + { + if (string->len) + g_string_append (string, "\n"); + + g_string_append_printf (string, WIDGET_DEPRECATED_MSG); + } + else + g_string_append_printf (string, WIDGET_DEPRECATED_FMT, + path_name, + glade_widget_adaptor_get_title (adaptor_iter), + catalog, target_major, target_minor); + + support_mask |= GLADE_SUPPORT_DEPRECATED; + } + g_free (catalog); + } + if (mask) + *mask = support_mask; + +} + +/** + * glade_project_verify_widget_adaptor: + * @project: A #GladeProject + * @adaptor: the #GladeWidgetAdaptor to verify + * @mask: a return location for a #GladeSupportMask + * + * Checks the supported state of this widget adaptor + * and generates a string to show in the UI describing why. + * + * Returns: A newly allocated string + */ +gchar * +glade_project_verify_widget_adaptor (GladeProject *project, + GladeWidgetAdaptor *adaptor, + GladeSupportMask *mask) +{ + GString *string = g_string_new (NULL); + gchar *ret = NULL; + + glade_project_verify_adaptor (project, adaptor, NULL, + string, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED, + TRUE, mask); + + /* there was a '\0' byte... */ + if (string->len > 0) + { + ret = string->str; + g_string_free (string, FALSE); + } + else + g_string_free (string, TRUE); + + + return ret; +} + + +/** + * glade_project_verify_project_for_ui: + * @project: A #GladeProject + * + * Checks the project and updates warning strings in the UI + */ +static void +glade_project_verify_project_for_ui (GladeProject *project) +{ + GList *list; + GladeWidget *widget; + + /* Sync displayable info here */ + for (list = project->priv->objects; list; list = list->next) + { + widget = glade_widget_get_from_gobject (list->data); + + /* Update the support warnings for widget's properties */ + glade_project_verify_properties (widget); + + /* Update the support warning for widget */ + glade_widget_verify (widget); + } +} + +/** + * glade_project_get_widget_by_name: + * @project: a #GladeProject + * @name: The user visible name of the widget we are looking for + * + * Searches under @ancestor in @project looking for a #GladeWidget named @name. + * + * Returns: (transfer none) (nullable): a pointer to the widget, + * %NULL if the widget does not exist + */ +GladeWidget * +glade_project_get_widget_by_name (GladeProject *project, const gchar *name) +{ + GList *list; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + g_return_val_if_fail (name != NULL, NULL); + + for (list = project->priv->objects; list; list = list->next) + { + GladeWidget *widget; + + widget = glade_widget_get_from_gobject (list->data); + + if (strcmp (glade_widget_get_name (widget), name) == 0) + return widget; + } + + return NULL; +} + +static void +glade_project_release_widget_name (GladeProject *project, + GladeWidget *gwidget, + const char *widget_name) +{ + glade_name_context_release_name (project->priv->widget_names, widget_name); +} + +/** + * glade_project_available_widget_name: + * @project: a #GladeProject + * @widget: the #GladeWidget intended to receive a new name + * @name: base name of the widget to create + * + * Checks whether @name is an appropriate name for @widget. + * + * Returns: whether the name is available or not. + */ +gboolean +glade_project_available_widget_name (GladeProject *project, + GladeWidget *widget, + const gchar *name) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE); + + if (!name || !name[0]) + return FALSE; + + return !glade_name_context_has_name (project->priv->widget_names, name); +} + +static void +glade_project_reserve_widget_name (GladeProject *project, + GladeWidget *gwidget, + const char *widget_name) +{ + if (!glade_project_available_widget_name (project, gwidget, widget_name)) + { + g_warning ("BUG: widget '%s' attempting to reserve an unavailable widget name '%s' !", + glade_widget_get_name (gwidget), widget_name); + return; + } + + /* Add to name context */ + glade_name_context_add_name (project->priv->widget_names, widget_name); +} + +/** + * glade_project_new_widget_name: + * @project: a #GladeProject + * @widget: the #GladeWidget intended to receive a new name, or %NULL + * @base_name: base name of the widget to create + * + * Creates a new name for a widget that doesn't collide with any of the names + * already in @project. This name will start with @base_name. + * + * Note the @widget parameter is ignored and preserved only for historical reasons. + * + * Returns: a string containing the new name, %NULL if there is not enough + * memory for this string + */ +gchar * +glade_project_new_widget_name (GladeProject *project, + GladeWidget *widget, + const gchar *base_name) +{ + gchar *name; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + g_return_val_if_fail (base_name && base_name[0], NULL); + + name = glade_name_context_new_name (project->priv->widget_names, base_name); + + return name; +} + +static gboolean +glade_project_get_iter_for_object (GladeProject *project, + GladeWidget *widget, + GtkTreeIter *iter) +{ + GtkTreeModel *model = project->priv->model; + GladeWidget *widget_iter = widget; + GList *parent_node, *hierarchy = NULL; + gboolean iter_valid; + + g_return_val_if_fail (widget, FALSE); + g_return_val_if_fail (GLADE_IS_WIDGET (widget), FALSE); + + if (!(iter_valid = gtk_tree_model_get_iter_first (model, iter))) + return FALSE; + + /* Generate widget hierarchy list */ + while ((widget_iter = glade_widget_get_parent (widget_iter))) + hierarchy = g_list_prepend (hierarchy, widget_iter); + + parent_node = hierarchy; + + do + { + gtk_tree_model_get (model, iter, 0, &widget_iter, -1); + g_object_unref (widget_iter); + + if (widget_iter == widget) + { + /* Object found */ + g_list_free (hierarchy); + return TRUE; + } + else if (parent_node && widget_iter == parent_node->data) + { + GtkTreeIter child_iter; + + if (gtk_tree_model_iter_children (model, &child_iter, iter)) + { + /* Parent found, adn child iter updated, continue looking */ + parent_node = g_list_next (parent_node); + *iter = child_iter; + continue; + } + else + { + /* Parent with no children? */ + g_warning ("Discrepancy found in TreeModel data proxy. " + "Can not get children iter for widget %s", + glade_widget_get_name (widget_iter)); + break; + } + } + + iter_valid = gtk_tree_model_iter_next (model, iter); + + } while (iter_valid); + + g_list_free (hierarchy); + return FALSE; +} + +/** + * glade_project_set_widget_name: + * @project: a #GladeProject + * @widget: the #GladeWidget to set a name on + * @name: the name to set. + * + * Sets @name on @widget in @project, if @name is not + * available then a new name will be used. + */ +void +glade_project_set_widget_name (GladeProject *project, + GladeWidget *widget, + const gchar *name) +{ + gchar *new_name; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (GLADE_IS_WIDGET (widget)); + g_return_if_fail (name && name[0]); + + if (strcmp (name, glade_widget_get_name (widget)) == 0) + return; + + /* Police the widget name */ + if (!glade_project_available_widget_name (project, widget, name)) + new_name = glade_project_new_widget_name (project, widget, name); + else + new_name = g_strdup (name); + + glade_project_reserve_widget_name (project, widget, new_name); + + /* Release old name and set new widget name */ + glade_project_release_widget_name (project, widget, glade_widget_get_name (widget)); + glade_widget_set_name (widget, new_name); + + g_signal_emit (G_OBJECT (project), + glade_project_signals[WIDGET_NAME_CHANGED], 0, widget); + + g_free (new_name); + + /* Notify views about the iter change */ + glade_project_widget_changed (project, widget); +} + +/** + * glade_project_check_reordered: + * @project: a #GladeProject + * @parent: the parent #GladeWidget + * @old_order: (element-type GObject): the old order to compare with + * + */ +void +glade_project_check_reordered (GladeProject *project, + GladeWidget *parent, + GList *old_order) +{ + GList *new_order, *l, *ll; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (GLADE_IS_WIDGET (parent)); + g_return_if_fail (glade_project_has_object (project, + glade_widget_get_object (parent))); + + new_order = glade_widget_get_children (parent); + + /* Check if the list changed */ + for (l = old_order, ll = new_order; + l && ll; + l = g_list_next (l), ll = g_list_next (ll)) + { + if (l->data != ll->data) + break; + } + + if (l || ll) + { + gint *order = g_new0 (gint, g_list_length (new_order)); + GtkTreeIter iter; + gint i; + + for (i = 0, l = new_order; l; l = g_list_next (l)) + { + GObject *obj = l->data; + GList *node = g_list_find (old_order, obj); + + g_assert (node); + + order[i] = g_list_position (old_order, node); + i++; + } + + /* Signal that the rows were reordered */ + glade_project_get_iter_for_object (project, parent, &iter); + gtk_tree_store_reorder (GTK_TREE_STORE (project->priv->model), &iter, order); + + g_free (order); + } + + g_list_free (new_order); +} + +static inline gboolean +glade_project_has_gwidget (GladeProject *project, GladeWidget *gwidget) +{ + return (glade_widget_get_project (gwidget) == project && + glade_widget_in_project (gwidget)); +} + +/** + * glade_project_add_object: + * @project: the #GladeProject the widget is added to + * @object: the #GObject to add + * + * Adds an object to the project. + */ +void +glade_project_add_object (GladeProject *project, GObject *object) +{ + GladeProjectPrivate *priv; + GladeWidget *gwidget; + GList *list, *children; + const gchar *name; + GtkTreeIter iter, *parent = NULL; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (G_IS_OBJECT (object)); + + /* We don't list placeholders */ + if (GLADE_IS_PLACEHOLDER (object)) + return; + + /* Only widgets accounted for in the catalog or widgets declared + * in the plugin with glade_widget_new_for_internal_child () are + * usefull in the project. + */ + if ((gwidget = glade_widget_get_from_gobject (object)) == NULL) + return; + + if (glade_project_has_gwidget (project, gwidget)) + { + /* FIXME: It's possible we need to notify the model iface if this + * happens to make sure the hierarchy is the same, I dont know, this + * happens when message dialogs with children are rebuilt but the + * hierarchy still looks good afterwards. */ + return; + } + + priv = project->priv; + + name = glade_widget_get_name (gwidget); + /* Make sure we have an exclusive name first... */ + if (!glade_project_available_widget_name (project, gwidget, name)) + { + gchar *new_name = glade_project_new_widget_name (project, gwidget, name); + + /* XXX Collect these errors and make a report at startup time */ + if (priv->loading) + g_warning ("Loading object '%s' with name conflict, renaming to '%s'", + name, new_name); + + glade_widget_set_name (gwidget, new_name); + name = glade_widget_get_name (gwidget); + + g_free (new_name); + } + + glade_project_reserve_widget_name (project, gwidget, name); + + glade_widget_set_project (gwidget, (gpointer) project); + glade_widget_set_in_project (gwidget, TRUE); + g_object_ref_sink (gwidget); + + /* Be sure to update the lists before emitting signals */ + if (glade_widget_get_parent (gwidget) == NULL) + priv->tree = g_list_append (priv->tree, object); + else if (glade_project_get_iter_for_object (project, + glade_widget_get_parent (gwidget), + &iter)) + { + parent = &iter; + } + + priv->objects = g_list_prepend (priv->objects, object); + gtk_tree_store_insert_with_values (GTK_TREE_STORE (priv->model), NULL, parent, -1, + 0, gwidget, -1); + + /* NOTE: Sensitive ordering here, we need to recurse after updating + * the tree model listeners (and update those listeners after our + * internal lists have been resolved), otherwise children are added + * before the parents (and the views dont like that). + */ + if ((children = glade_widget_get_children (gwidget)) != NULL) + { + for (list = children; list && list->data; list = list->next) + glade_project_add_object (project, G_OBJECT (list->data)); + g_list_free (children); + } + + /* Update user visible compatibility info */ + glade_project_verify_properties (gwidget); + + g_signal_emit (G_OBJECT (project), + glade_project_signals[ADD_WIDGET], 0, gwidget); +} + +/** + * glade_project_has_object: + * @project: the #GladeProject the widget is added to + * @object: the #GObject to search + * + * Returns: whether this object is in this project. + */ +gboolean +glade_project_has_object (GladeProject *project, GObject *object) +{ + GladeWidget *gwidget; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + g_return_val_if_fail (G_IS_OBJECT (object), FALSE); + + gwidget = glade_widget_get_from_gobject (object); + + g_return_val_if_fail (GLADE_IS_WIDGET (gwidget), FALSE); + + return glade_project_has_gwidget (project, gwidget); +} + +void +glade_project_widget_changed (GladeProject *project, GladeWidget *gwidget) +{ + GtkTreeIter iter; + GtkTreePath *path; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (GLADE_IS_WIDGET (gwidget)); + g_return_if_fail (glade_project_has_gwidget (project, gwidget)); + + glade_project_get_iter_for_object (project, gwidget, &iter); + path = gtk_tree_model_get_path (project->priv->model, &iter); + gtk_tree_model_row_changed (project->priv->model, path, &iter); + gtk_tree_path_free (path); +} + +/** + * glade_project_remove_object: + * @project: a #GladeProject + * @object: the #GObject to remove + * + * Removes @object from @project. + * + * Note that when removing the #GObject from the project we + * don't change ->project in the associated #GladeWidget; this + * way UNDO can work. + */ +void +glade_project_remove_object (GladeProject *project, GObject *object) +{ + GladeWidget *gwidget; + GList *list, *children; + gchar *preview_pid; + GtkTreeIter iter; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (G_IS_OBJECT (object)); + + if (GLADE_IS_PLACEHOLDER (object)) + return; + + if ((gwidget = glade_widget_get_from_gobject (object)) == NULL) + { + if (g_list_find (project->priv->objects, object)) + { + project->priv->tree = g_list_remove_all (project->priv->tree, object); + project->priv->objects = g_list_remove_all (project->priv->objects, object); + project->priv->selection = g_list_remove_all (project->priv->selection, object); + g_warning ("Internal data model error, removing object %p %s without a GladeWidget wrapper", + object, G_OBJECT_TYPE_NAME (object)); + } + return; + } + + if (!glade_project_has_object (project, object)) + return; + + /* Recurse and remove deepest children first */ + if ((children = glade_widget_get_children (gwidget)) != NULL) + { + for (list = children; list && list->data; list = list->next) + glade_project_remove_object (project, G_OBJECT (list->data)); + g_list_free (children); + } + + /* Update UI since this could take a while, specially disposing a large project */ + while (gtk_events_pending ()) + gtk_main_iteration (); + + /* Remove selection and release name from the name context */ + glade_project_selection_remove (project, object, TRUE); + glade_project_release_widget_name (project, gwidget, + glade_widget_get_name (gwidget)); + + g_signal_emit (G_OBJECT (project), + glade_project_signals[REMOVE_WIDGET], 0, gwidget); + + /* Update internal data structure (remove from lists) */ + project->priv->tree = g_list_remove (project->priv->tree, object); + project->priv->objects = g_list_remove (project->priv->objects, object); + + if (glade_project_get_iter_for_object (project, gwidget, &iter)) + gtk_tree_store_remove (GTK_TREE_STORE (project->priv->model), &iter); + else + g_warning ("Internal data model error, object %p %s not found in tree model", + object, G_OBJECT_TYPE_NAME (object)); + + if ((preview_pid = g_object_get_data (G_OBJECT (gwidget), "preview"))) + g_hash_table_remove (project->priv->previews, preview_pid); + + /* Unset the project pointer on the GladeWidget */ + glade_widget_set_project (gwidget, NULL); + glade_widget_set_in_project (gwidget, FALSE); + g_object_unref (gwidget); +} + +/******************************************************************* + * Other API * + *******************************************************************/ +/** + * glade_project_set_modified: + * @project: a #GladeProject + * @modified: Whether the project should be set as modified or not + * @modification: The first #GladeCommand which caused the project to have unsaved changes + * + * Set's whether a #GladeProject should be flagged as modified or not. This is useful + * for indicating that a project has unsaved changes. If @modified is #TRUE, then + * @modification will be recorded as the first change which caused the project to + * have unsaved changes. @modified is #FALSE then @modification will be ignored. + * + * If @project is already flagged as modified, then calling this method with + * @modified as #TRUE, will have no effect. Likewise, if @project is unmodified + * then calling this method with @modified as #FALSE, will have no effect. + * + */ +static void +glade_project_set_modified (GladeProject *project, gboolean modified) +{ + GladeProjectPrivate *priv; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + priv = project->priv; + + if (priv->modified != modified) + { + priv->modified = !priv->modified; + + if (!priv->modified) + { + priv->first_modification = project->priv->prev_redo_item; + priv->first_modification_is_na = FALSE; + } + + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_MODIFIED]); + } +} + +/** + * glade_project_get_modified: + * @project: a #GladeProject + * + * Get's whether the project has been modified since it was last saved. + * + * Returns: %TRUE if the project has been modified since it was last saved + */ +gboolean +glade_project_get_modified (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + return project->priv->modified; +} + +void +glade_project_set_pointer_mode (GladeProject *project, GladePointerMode mode) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (project->priv->pointer_mode != mode) + { + project->priv->pointer_mode = mode; + + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_POINTER_MODE]); + } +} + +GladePointerMode +glade_project_get_pointer_mode (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + return project->priv->pointer_mode; +} + +void +glade_project_set_template (GladeProject *project, GladeWidget *widget) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (widget == NULL || GLADE_IS_WIDGET (widget)); + + if (widget) + { + GObject *object = glade_widget_get_object (widget); + + g_return_if_fail (GTK_IS_WIDGET (object)); + g_return_if_fail (glade_widget_get_parent (widget) == NULL); + g_return_if_fail (glade_widget_get_project (widget) == project); + } + + /* Let's not add any strong reference here, we already own the widget */ + if (project->priv->template != widget) + { + if (project->priv->template) + glade_widget_set_is_composite (project->priv->template, FALSE); + + project->priv->template = widget; + + if (project->priv->template) + glade_widget_set_is_composite (project->priv->template, TRUE); + + glade_project_verify_project_for_ui (project); + + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_TEMPLATE]); + } +} + +/** + * glade_project_get_template: + * @project: a #GladeProject + * + * Returns: (transfer none): a #GladeWidget + */ +GladeWidget * +glade_project_get_template (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + return project->priv->template; +} + +/** + * glade_project_set_add_item: + * @project: a #GladeProject + * @adaptor: (transfer full): a #GladeWidgetAdaptor + */ +void +glade_project_set_add_item (GladeProject *project, GladeWidgetAdaptor *adaptor) +{ + GladeProjectPrivate *priv; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + priv = project->priv; + + if (priv->add_item != adaptor) + { + priv->add_item = adaptor; + + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_ADD_ITEM]); + } +} + +/** + * glade_project_get_add_item: + * @project: a #GladeProject + * + * Returns: (transfer none): a #GladeWidgetAdaptor + */ +GladeWidgetAdaptor * +glade_project_get_add_item (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->add_item; +} + +void +glade_project_set_target_version (GladeProject *project, + const gchar *catalog, + gint major, + gint minor) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (catalog && catalog[0]); + g_return_if_fail (major >= 0); + g_return_if_fail (minor >= 0); + + g_hash_table_insert (project->priv->target_versions_major, + g_strdup (catalog), GINT_TO_POINTER ((int) major)); + g_hash_table_insert (project->priv->target_versions_minor, + g_strdup (catalog), GINT_TO_POINTER ((int) minor)); + + glade_project_verify_project_for_ui (project); + + g_signal_emit (project, glade_project_signals[TARGETS_CHANGED], 0); +} + +static void +glade_project_set_readonly (GladeProject *project, gboolean readonly) +{ + g_assert (GLADE_IS_PROJECT (project)); + + if (project->priv->readonly != readonly) + { + project->priv->readonly = readonly; + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_READ_ONLY]); + } +} + +/** + * glade_project_get_target_version: + * @project: a #GladeProject + * @catalog: the name of the catalog @project includes + * @major: the return location for the target major version + * @minor: the return location for the target minor version + * + * Fetches the target version of the @project for @catalog. + * + */ +void +glade_project_get_target_version (GladeProject *project, + const gchar *catalog, + gint *major, + gint *minor) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (catalog && catalog[0]); + g_return_if_fail (major && minor); + + *major = GPOINTER_TO_INT + (g_hash_table_lookup (project->priv->target_versions_major, catalog)); + *minor = GPOINTER_TO_INT + (g_hash_table_lookup (project->priv->target_versions_minor, catalog)); +} + +/** + * glade_project_get_readonly: + * @project: a #GladeProject + * + * Gets whether the project is read only or not + * + * Returns: TRUE if project is read only + */ +gboolean +glade_project_get_readonly (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + return project->priv->readonly; +} + +/** + * glade_project_selection_changed: + * @project: a #GladeProject + * + * Causes @project to emit a "selection_changed" signal. + */ +void +glade_project_selection_changed (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + + g_signal_emit (G_OBJECT (project), + glade_project_signals[SELECTION_CHANGED], 0); + + /* Cancel any idle we have */ + g_clear_handle_id (&project->priv->selection_changed_id, g_source_remove); +} + +static gboolean +selection_change_idle (gpointer data) +{ + GladeProject *project = data; + project->priv->selection_changed_id = 0; + glade_project_selection_changed (project); + return G_SOURCE_REMOVE; +} + +void +glade_project_queue_selection_changed (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (project->priv->selection_changed_id) + return; + + project->priv->selection_changed_id = g_idle_add (selection_change_idle, project); +} + +static void +glade_project_set_has_selection (GladeProject *project, gboolean has_selection) +{ + g_assert (GLADE_IS_PROJECT (project)); + + if (project->priv->has_selection != has_selection) + { + project->priv->has_selection = has_selection; + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_HAS_SELECTION]); + } +} + +/** + * glade_project_get_has_selection: + * @project: a #GladeProject + * + * Returns: whether @project currently has a selection + */ +gboolean +glade_project_get_has_selection (GladeProject *project) +{ + g_assert (GLADE_IS_PROJECT (project)); + + return project->priv->has_selection; +} + +/** + * glade_project_is_selected: + * @project: a #GladeProject + * @object: a #GObject + * + * Returns: whether @object is in @project selection + */ +gboolean +glade_project_is_selected (GladeProject *project, GObject *object) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + return (g_list_find (project->priv->selection, object)) != NULL; +} + +/** + * glade_project_selection_clear: + * @project: a #GladeProject + * @emit_signal: whether or not to emit a signal indication a selection change + * + * Clears @project's selection chain + * + * If @emit_signal is %TRUE, calls glade_project_selection_changed(). + */ +void +glade_project_selection_clear (GladeProject *project, gboolean emit_signal) +{ + GList *l; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (project->priv->selection == NULL) + return; + + for (l = project->priv->selection; l; l = l->next) + { + if (GTK_IS_WIDGET (l->data)) + gtk_widget_queue_draw (GTK_WIDGET (l->data)); + } + + g_list_free (project->priv->selection); + project->priv->selection = NULL; + glade_project_set_has_selection (project, FALSE); + + if (emit_signal) + glade_project_selection_changed (project); +} + +/** + * glade_project_selection_remove: + * @project: a #GladeProject + * @object: a #GObject in @project + * @emit_signal: whether or not to emit a signal + * indicating a selection change + * + * Removes @object from the selection chain of @project + * + * If @emit_signal is %TRUE, calls glade_project_selection_changed(). + */ +void +glade_project_selection_remove (GladeProject *project, + GObject *object, + gboolean emit_signal) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (G_IS_OBJECT (object)); + + if (glade_project_is_selected (project, object)) + { + project->priv->selection = + g_list_remove (project->priv->selection, object); + if (project->priv->selection == NULL) + glade_project_set_has_selection (project, FALSE); + if (emit_signal) + glade_project_selection_changed (project); + } +} + +/** + * glade_project_selection_add: + * @project: a #GladeProject + * @object: a #GObject in @project + * @emit_signal: whether or not to emit a signal indicating + * a selection change + * + * Adds @object to the selection chain of @project + * + * If @emit_signal is %TRUE, calls glade_project_selection_changed(). + */ +void +glade_project_selection_add (GladeProject *project, + GObject *object, + gboolean emit_signal) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (glade_project_has_object (project, object)); + + if (glade_project_is_selected (project, object) == FALSE) + { + gboolean toggle_has_selection = (project->priv->selection == NULL); + + if (GTK_IS_WIDGET (object)) + gtk_widget_queue_draw (GTK_WIDGET (object)); + + project->priv->selection = + g_list_prepend (project->priv->selection, object); + + if (toggle_has_selection) + glade_project_set_has_selection (project, TRUE); + + if (emit_signal) + glade_project_selection_changed (project); + } +} + +/** + * glade_project_selection_set: + * @project: a #GladeProject + * @object: a #GObject in @project + * @emit_signal: whether or not to emit a signal + * indicating a selection change + * + * Set the selection in @project to @object + * + * If @emit_signal is %TRUE, calls glade_project_selection_changed(). + */ +void +glade_project_selection_set (GladeProject *project, + GObject *object, + gboolean emit_signal) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (G_IS_OBJECT (object)); + g_return_if_fail (glade_project_has_object (project, object)); + + if (glade_project_is_selected (project, object) == FALSE || + g_list_length (project->priv->selection) != 1) + { + glade_project_selection_clear (project, FALSE); + glade_project_selection_add (project, object, emit_signal); + } +} + +/** + * glade_project_selection_get: + * @project: a #GladeProject + * + * Returns: (transfer none) (element-type GtkWidget): a #GList containing + * the #GtkWidget items currently selected in @project + */ +GList * +glade_project_selection_get (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->selection; +} + +/** + * glade_project_required_libs: + * @project: a #GladeProject + * + * Returns: (transfer full) (element-type utf8): a #GList of allocated strings + * which are the names of the required catalogs for this project + */ +GList * +glade_project_required_libs (GladeProject *project) +{ + GList *l, *required = NULL; + + /* Assume GTK+ catalog here */ + required = g_list_prepend (required, _glade_catalog_get_catalog ("gtk+")); + + for (l = project->priv->objects; l; l = g_list_next (l)) + { + GladeWidget *gwidget = glade_widget_get_from_gobject (l->data); + GladeCatalog *catalog; + gchar *name = NULL; + + g_assert (gwidget); + + g_object_get (glade_widget_get_adaptor (gwidget), "catalog", &name, NULL); + + if ((catalog = _glade_catalog_get_catalog (name))) + { + if (!g_list_find (required, catalog)) + required = g_list_prepend (required, catalog); + } + + g_free (name); + } + + /* Sort by dependency */ + required = _glade_catalog_tsort (required); + + /* Convert list of GladeCatalog to list of names */ + for (l = required; l; l = g_list_next (l)) + l->data = g_strdup (glade_catalog_get_name (l->data)); + + for (l = project->priv->unknown_catalogs; l; l = g_list_next (l)) + { + CatalogInfo *data = l->data; + /* Keep position to make sure we do not create a diff when saving */ + required = g_list_insert (required, g_strdup (data->catalog), data->position); + } + + return required; +} + +/** + * glade_project_undo: + * @project: a #GladeProject + * + * Undoes a #GladeCommand in this project. + */ +void +glade_project_undo (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + GLADE_PROJECT_GET_CLASS (project)->undo (project); +} + +/** + * glade_project_redo: + * @project: a #GladeProject + * + * Redoes a #GladeCommand in this project. + */ +void +glade_project_redo (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + GLADE_PROJECT_GET_CLASS (project)->redo (project); +} + +/** + * glade_project_next_undo_item: + * @project: a #GladeProject + * + * Gets the next undo item on @project's command stack. + * + * Returns: (transfer none): the #GladeCommand + */ +GladeCommand * +glade_project_next_undo_item (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + return GLADE_PROJECT_GET_CLASS (project)->next_undo_item (project); +} + +/** + * glade_project_next_redo_item: + * @project: a #GladeProject + * + * Gets the next redo item on @project's command stack. + * + * Returns: (transfer none): the #GladeCommand + */ +GladeCommand * +glade_project_next_redo_item (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + return GLADE_PROJECT_GET_CLASS (project)->next_redo_item (project); +} + +/** + * glade_project_push_undo: + * @project: a #GladeProject + * @cmd: the #GladeCommand + * + * Pushes a newly created #GladeCommand onto @projects stack. + */ +void +glade_project_push_undo (GladeProject *project, GladeCommand *cmd) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (GLADE_IS_COMMAND (cmd)); + + GLADE_PROJECT_GET_CLASS (project)->push_undo (project, cmd); +} + +static GList * +walk_command (GList *list, gboolean forward) +{ + GladeCommand *cmd = list->data; + GladeCommand *next_cmd; + + if (forward) + list = list->next; + else + list = list->prev; + + next_cmd = list ? list->data : NULL; + + while (list && + glade_command_group_id (next_cmd) != 0 && + glade_command_group_id (next_cmd) == glade_command_group_id (cmd)) + { + if (forward) + list = list->next; + else + list = list->prev; + + if (list) + next_cmd = list->data; + } + + return list; +} + +static void +undo_item_activated (GtkMenuItem *item, GladeProject *project) +{ + gint index, next_index; + + GladeCommand *cmd = g_object_get_data (G_OBJECT (item), "command-data"); + GladeCommand *next_cmd; + + index = g_list_index (project->priv->undo_stack, cmd); + + do + { + next_cmd = glade_project_next_undo_item (project); + next_index = g_list_index (project->priv->undo_stack, next_cmd); + + glade_project_undo (project); + + } + while (next_index > index); +} + +static void +redo_item_activated (GtkMenuItem *item, GladeProject *project) +{ + gint index, next_index; + + GladeCommand *cmd = g_object_get_data (G_OBJECT (item), "command-data"); + GladeCommand *next_cmd; + + index = g_list_index (project->priv->undo_stack, cmd); + + do + { + next_cmd = glade_project_next_redo_item (project); + next_index = g_list_index (project->priv->undo_stack, next_cmd); + + glade_project_redo (project); + + } + while (next_index < index); +} + + +/** + * glade_project_undo_items: + * @project: A #GladeProject + * + * Creates a menu of the undo items in the project stack + * + * Returns: (transfer full): A newly created menu + */ +GtkWidget * +glade_project_undo_items (GladeProject *project) +{ + GtkWidget *menu = NULL; + GtkWidget *item; + GladeCommand *cmd; + GList *l; + + g_return_val_if_fail (project != NULL, NULL); + + for (l = project->priv->prev_redo_item; l; l = walk_command (l, FALSE)) + { + cmd = l->data; + + if (!menu) + menu = gtk_menu_new (); + + item = gtk_menu_item_new_with_label (glade_command_description (cmd)); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item)); + g_object_set_data (G_OBJECT (item), "command-data", cmd); + + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (undo_item_activated), project); + + } + + return menu; +} + +/** + * glade_project_redo_items: + * @project: A #GladeProject + * + * Creates a menu of the undo items in the project stack + * + * Returns: (transfer full): A newly created menu + */ +GtkWidget * +glade_project_redo_items (GladeProject *project) +{ + GtkWidget *menu = NULL; + GtkWidget *item; + GladeCommand *cmd; + GList *l; + + g_return_val_if_fail (project != NULL, NULL); + + for (l = project->priv->prev_redo_item ? + project->priv->prev_redo_item->next : + project->priv->undo_stack; l; l = walk_command (l, TRUE)) + { + cmd = l->data; + + if (!menu) + menu = gtk_menu_new (); + + item = gtk_menu_item_new_with_label (glade_command_description (cmd)); + gtk_widget_show (item); + gtk_menu_shell_append (GTK_MENU_SHELL (menu), GTK_WIDGET (item)); + g_object_set_data (G_OBJECT (item), "command-data", cmd); + + g_signal_connect (G_OBJECT (item), "activate", + G_CALLBACK (redo_item_activated), project); + + } + + return menu; +} + +void +glade_project_reset_path (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + project->priv->path = (g_free (project->priv->path), NULL); +} + +/** + * glade_project_resource_fullpath: + * @project: The #GladeProject. + * @resource: The resource basename + * + * Project resource strings are always relative, this function transforms a + * path relative to project to a full path. + * + * Returns: A newly allocated string holding the + * full path to the resource. + */ +gchar * +glade_project_resource_fullpath (GladeProject *project, const gchar *resource) +{ + gchar *fullpath, *project_dir = NULL; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + if (project->priv->path == NULL) + project_dir = g_get_current_dir (); + else + project_dir = g_path_get_dirname (project->priv->path); + + if (project->priv->resource_path) + { + if (g_path_is_absolute (project->priv->resource_path)) + fullpath = + g_build_filename (project->priv->resource_path, resource, NULL); + else + fullpath = + g_build_filename (project_dir, project->priv->resource_path, + resource, NULL); + } + else + fullpath = g_build_filename (project_dir, resource, NULL); + + g_free (project_dir); + return fullpath; +} + +/** + * glade_project_widget_visibility_changed: + * @project: The #GladeProject. + * @widget: The widget which visibility changed + * @visible: widget visibility value + * + * Emits GladeProject::widget-visibility-changed signal + * + */ +void +glade_project_widget_visibility_changed (GladeProject *project, + GladeWidget *widget, + gboolean visible) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (project == glade_widget_get_project (widget)); + + g_signal_emit (project, glade_project_signals[WIDGET_VISIBILITY_CHANGED], 0, + widget, visible); +} + +const gchar * +glade_project_get_path (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->path; +} + +gchar * +glade_project_get_name (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + if (project->priv->path) + return g_filename_display_basename (project->priv->path); + else + return g_strdup_printf (_("Unsaved %i"), project->priv->unsaved_number); +} + +/** + * glade_project_is_loading: + * @project: A #GladeProject + * + * Returns: Whether the project is being loaded or not + * + */ +gboolean +glade_project_is_loading (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), FALSE); + + return project->priv->loading; +} + +time_t +glade_project_get_file_mtime (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), 0); + + return project->priv->mtime; +} + +/** + * glade_project_get_objects: + * @project: a GladeProject + * + * Returns: (transfer none) (element-type GObject): List of all objects in this project + */ +const GList * +glade_project_get_objects (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->objects; +} + +/** + * glade_project_properties: + * @project: A #GladeProject + * + * Runs a document properties dialog for @project. + */ +void +glade_project_properties (GladeProject *project) +{ + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (project->priv->prefs_dialog) + { + glade_project_verify (project, FALSE, + GLADE_VERIFY_VERSIONS | + GLADE_VERIFY_DEPRECATIONS | + GLADE_VERIFY_UNRECOGNIZED); + gtk_window_present (GTK_WINDOW (project->priv->prefs_dialog)); + } +} + +gchar * +glade_project_display_dependencies (GladeProject *project) +{ + GList *catalogs, *l; + GString *string; + + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + string = g_string_new (""); + + catalogs = glade_project_required_libs (project); + for (l = catalogs; l; l = l->next) + { + gchar *catalog = l->data; + gint major = 0, minor = 0; + + glade_project_get_target_version (project, catalog, &major, &minor); + + if (l != catalogs) + g_string_append (string, ", "); + + /* Capitalize GTK+ */ + if (strcmp (catalog, "gtk+") == 0) + g_string_append_printf (string, "GTK+ >= %d.%d", major, minor); + else if (major && minor) + g_string_append_printf (string, "%s >= %d.%d", catalog, major, minor); + else + g_string_append_printf (string, "%s", catalog); + + g_free (catalog); + } + g_list_free (catalogs); + + return g_string_free (string, FALSE); +} + +/** + * glade_project_toplevels: + * @project: a #GladeProject + * + * Returns: (transfer none) (element-type GtkWidget): a #GList containing + * the #GtkWidget toplevel items in @project + */ +GList * +glade_project_toplevels (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->tree; +} + +/** + * glade_project_set_translation_domain: + * @project: a #GladeProject + * @domain: the translation domain + * + * Set the project translation domain. + */ +void +glade_project_set_translation_domain (GladeProject *project, const gchar *domain) +{ + GladeProjectPrivate *priv; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + priv = project->priv; + + if (g_strcmp0 (priv->translation_domain, domain)) + { + g_free (priv->translation_domain); + priv->translation_domain = g_strdup (domain); + + g_object_notify_by_pspec (G_OBJECT (project), + glade_project_props[PROP_TRANSLATION_DOMAIN]); + } +} + +/** + * glade_project_get_translation_domain: + * @project: a #GladeProject + * + * Returns: the translation domain + */ +const gchar * +glade_project_get_translation_domain (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->translation_domain; +} + +static void +glade_project_css_provider_remove_forall (GtkWidget *widget, gpointer data) +{ + gtk_style_context_remove_provider (gtk_widget_get_style_context (widget), + GTK_STYLE_PROVIDER (data)); + + if (GTK_IS_CONTAINER (widget)) + gtk_container_forall (GTK_CONTAINER (widget), glade_project_css_provider_remove_forall, data); +} + +static inline void +glade_project_css_provider_refresh (GladeProject *project, gboolean remove) +{ + GladeProjectPrivate *priv = project->priv; + GtkCssProvider *provider = priv->css_provider; + const GList *l; + + for (l = priv->tree; l; l = g_list_next (l)) + { + GObject *object = l->data; + + if (!GTK_IS_WIDGET (object) || GLADE_IS_OBJECT_STUB (object)) + continue; + + if (remove) + glade_project_css_provider_remove_forall (GTK_WIDGET (object), provider); + else + glade_project_set_css_provider_forall (GTK_WIDGET (object), provider); + } +} + +static void +on_css_monitor_changed (GFileMonitor *monitor, + GFile *file, + GFile *other_file, + GFileMonitorEvent event_type, + GladeProject *project) +{ + GError *error = NULL; + + gtk_css_provider_load_from_file (project->priv->css_provider, file, &error); + + if (error) + { + g_message ("CSS parsing failed: %s", error->message); + g_error_free (error); + } +} + +/** + * glade_project_set_css_provider_path: + * @project: a #GladeProject + * @path: a CSS file path + * + * Set the custom CSS provider path to use in @project + */ +void +glade_project_set_css_provider_path (GladeProject *project, const gchar *path) +{ + GladeProjectPrivate *priv; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + priv = project->priv; + + if (g_strcmp0 (priv->css_provider_path, path) != 0) + { + g_free (priv->css_provider_path); + priv->css_provider_path = g_strdup (path); + + g_clear_object (&priv->css_monitor); + + if (priv->css_provider) + { + glade_project_css_provider_refresh (project, TRUE); + g_clear_object (&priv->css_provider); + } + + if (priv->css_provider_path && + g_file_test (priv->css_provider_path, G_FILE_TEST_IS_REGULAR)) + { + GFile *file = g_file_new_for_path (priv->css_provider_path); + + priv->css_provider = GTK_CSS_PROVIDER (gtk_css_provider_new ()); + g_object_ref_sink (priv->css_provider); + gtk_css_provider_load_from_file (priv->css_provider, file, NULL); + + g_clear_object (&priv->css_monitor); + priv->css_monitor = g_file_monitor_file (file, G_FILE_MONITOR_NONE, NULL, NULL); + g_object_ref_sink (priv->css_monitor); + g_signal_connect_object (priv->css_monitor, "changed", + G_CALLBACK (on_css_monitor_changed), project, 0); + + glade_project_css_provider_refresh (project, FALSE); + g_object_unref (file); + } + + g_object_notify_by_pspec (G_OBJECT (project), glade_project_props[PROP_CSS_PROVIDER_PATH]); + } +} + +/** + * glade_project_get_css_provider_path: + * @project: a #GladeProject + * + * Returns: the CSS path of the custom provider used for @project + */ +const gchar * +glade_project_get_css_provider_path (GladeProject *project) +{ + g_return_val_if_fail (GLADE_IS_PROJECT (project), NULL); + + return project->priv->css_provider_path; +} + +/************************************************* + * Command Central * + *************************************************/ +static gboolean +widget_contains_unknown_type (GladeWidget *widget) +{ + GList *list, *l; + GObject *object; + gboolean has_unknown = FALSE; + + object = glade_widget_get_object (widget); + + if (GLADE_IS_OBJECT_STUB (object)) + return TRUE; + + list = glade_widget_get_children (widget); + for (l = list; l && has_unknown == FALSE; l = l->next) + { + GladeWidget *child = glade_widget_get_from_gobject (l->data); + + has_unknown = widget_contains_unknown_type (child); + } + g_list_free (list); + + return has_unknown; +} + +void +glade_project_copy_selection (GladeProject *project) +{ + GList *widgets = NULL, *list; + gboolean has_unknown = FALSE; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (glade_project_is_loading (project)) + return; + + if (!project->priv->selection) + { + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, _("No widget selected.")); + return; + } + + for (list = project->priv->selection; list && list->data; list = list->next) + { + GladeWidget *widget = glade_widget_get_from_gobject (list->data); + + if (widget_contains_unknown_type (widget)) + has_unknown = TRUE; + else + widgets = g_list_prepend (widgets, glade_widget_dup (widget, FALSE)); + } + + if (has_unknown) + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, _("Unable to copy unrecognized widget type.")); + + glade_clipboard_add (glade_app_get_clipboard (), widgets); + g_list_free (widgets); +} + +void +glade_project_command_cut (GladeProject *project) +{ + GList *widgets = NULL, *list; + gboolean has_unknown = FALSE; + gboolean failed = FALSE; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (glade_project_is_loading (project)) + return; + + for (list = project->priv->selection; list && list->data; list = list->next) + { + GladeWidget *widget = glade_widget_get_from_gobject (list->data); + + if (widget_contains_unknown_type (widget)) + has_unknown = TRUE; + else + widgets = g_list_prepend (widgets, widget); + } + + if (failed == FALSE && widgets != NULL) + glade_command_cut (widgets); + else if (has_unknown) + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, _("Unable to cut unrecognized widget type")); + else if (widgets == NULL) + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, _("No widget selected.")); + + if (widgets) + g_list_free (widgets); +} + +void +glade_project_command_paste (GladeProject *project, + GladePlaceholder *placeholder) +{ + GladeClipboard *clipboard; + GList *list; + GladeWidget *widget = NULL, *parent; + gint placeholder_relations = 0; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (glade_project_is_loading (project)) + return; + + if (placeholder) + { + if (glade_placeholder_get_project (placeholder) == NULL || + glade_project_is_loading (glade_placeholder_get_project (placeholder))) + return; + } + + list = project->priv->selection; + clipboard = glade_app_get_clipboard (); + + /* If there is a selection, paste in to the selected widget, otherwise + * paste into the placeholder's parent, or at the toplevel + */ + parent = list ? glade_widget_get_from_gobject (list->data) : + (placeholder) ? glade_placeholder_get_parent (placeholder) : NULL; + + widget = glade_clipboard_widgets (clipboard) ? glade_clipboard_widgets (clipboard)->data : NULL; + + /* Ignore parent argument if we are pasting a toplevel + */ + if (g_list_length (glade_clipboard_widgets (clipboard)) == 1 && + widget && GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (glade_widget_get_adaptor (widget))) + parent = NULL; + + /* Check if parent is actually a container of any sort */ + if (parent && !glade_widget_adaptor_is_container (glade_widget_get_adaptor (parent))) + { + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, + _("Unable to paste to the selected parent")); + return; + } + + /* Check if selection is good */ + if (project->priv->selection) + { + if (g_list_length (project->priv->selection) != 1) + { + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, + _("Unable to paste to multiple widgets")); + + return; + } + } + + /* Check if we have anything to paste */ + if (g_list_length (glade_clipboard_widgets (clipboard)) == 0) + { + glade_util_ui_message (glade_app_get_window (), GLADE_UI_INFO, NULL, + _("No widget on the clipboard")); + + return; + } + + /* Check that the underlying adaptor allows the paste */ + if (parent) + { + for (list = glade_clipboard_widgets (clipboard); list && list->data; list = list->next) + { + widget = list->data; + + if (!glade_widget_add_verify (parent, widget, TRUE)) + return; + } + } + + + /* Check that we have compatible heirarchies */ + for (list = glade_clipboard_widgets (clipboard); list && list->data; list = list->next) + { + widget = list->data; + + if (!GLADE_WIDGET_ADAPTOR_IS_TOPLEVEL (glade_widget_get_adaptor (widget)) && parent) + { + /* Count placeholder relations + */ + if (glade_widget_placeholder_relation (parent, widget)) + placeholder_relations++; + } + } + + g_assert (widget); + + /* A GladeWidget that doesnt use placeholders can only paste one + * at a time + * + * XXX: Not sure if this has to be true. + */ + if (GTK_IS_WIDGET (glade_widget_get_object (widget)) && + parent && !GLADE_WIDGET_ADAPTOR_USE_PLACEHOLDERS (glade_widget_get_adaptor (parent)) && + g_list_length (glade_clipboard_widgets (clipboard)) != 1) + { + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, + _("Only one widget can be pasted at a " + "time to this container")); + return; + } + + /* Check that enough placeholders are available */ + if (parent && + GLADE_WIDGET_ADAPTOR_USE_PLACEHOLDERS (glade_widget_get_adaptor (parent)) && + glade_util_count_placeholders (parent) < placeholder_relations) + { + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, + _("Insufficient amount of placeholders in " + "target container")); + return; + } + + glade_command_paste (glade_clipboard_widgets (clipboard), parent, placeholder, project); +} + +void +glade_project_command_delete (GladeProject *project) +{ + GList *widgets = NULL, *list; + GladeWidget *widget; + gboolean failed = FALSE; + + g_return_if_fail (GLADE_IS_PROJECT (project)); + + if (glade_project_is_loading (project)) + return; + + for (list = project->priv->selection; list && list->data; list = list->next) + { + widget = glade_widget_get_from_gobject (list->data); + widgets = g_list_prepend (widgets, widget); + } + + if (failed == FALSE && widgets != NULL) + glade_command_delete (widgets); + else if (widgets == NULL) + glade_util_ui_message (glade_app_get_window (), + GLADE_UI_INFO, NULL, _("No widget selected.")); + + if (widgets) + g_list_free (widgets); +} + +/* Private */ + +void +_glade_project_emit_add_signal_handler (GladeWidget *widget, + const GladeSignal *signal) +{ + GladeProject *project = glade_widget_get_project (widget); + + if (project) + g_signal_emit (project, glade_project_signals[ADD_SIGNAL_HANDLER], 0, + widget, signal); +} + +void +_glade_project_emit_remove_signal_handler (GladeWidget *widget, + const GladeSignal *signal) +{ + GladeProject *project = glade_widget_get_project (widget); + + if (project) + g_signal_emit (project, glade_project_signals[REMOVE_SIGNAL_HANDLER], 0, + widget, signal); + +} + +void +_glade_project_emit_change_signal_handler (GladeWidget *widget, + const GladeSignal *old_signal, + const GladeSignal *new_signal) +{ + GladeProject *project = glade_widget_get_project (widget); + + if (project) + g_signal_emit (project, glade_project_signals[CHANGE_SIGNAL_HANDLER], 0, + widget, old_signal, new_signal); +} + +void +_glade_project_emit_activate_signal_handler (GladeWidget *widget, + const GladeSignal *signal) +{ + GladeProject *project = glade_widget_get_project (widget); + + if (project) + g_signal_emit (project, glade_project_signals[ACTIVATE_SIGNAL_HANDLER], 0, + widget, signal); + +} + diff --git a/gladeui/glade-project.h b/gladeui/glade-project.h new file mode 100644 index 0000000..0d4c093 --- /dev/null +++ b/gladeui/glade-project.h @@ -0,0 +1,289 @@ +#ifndef __GLADE_PROJECT_H__ +#define __GLADE_PROJECT_H__ + +#include +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PROJECT (glade_project_get_type ()) +#define GLADE_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PROJECT, GladeProject)) +#define GLADE_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PROJECT, GladeProjectClass)) +#define GLADE_IS_PROJECT(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PROJECT)) +#define GLADE_IS_PROJECT_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PROJECT)) +#define GLADE_PROJECT_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PROJECT, GladeProjectClass)) + +typedef struct _GladeProjectPrivate GladeProjectPrivate; +typedef struct _GladeProjectClass GladeProjectClass; + +/** + * GladePointerMode: + * @GLADE_POINTER_SELECT: Mouse pointer used for selecting widgets + * @GLADE_POINTER_ADD_WIDGET: Mouse pointer used for adding widgets + * @GLADE_POINTER_DRAG_RESIZE: Mouse pointer used for dragging and + * resizing widgets in containers + * @GLADE_POINTER_MARGIN_EDIT: Mouse pointer used to edit widget margins + * @GLADE_POINTER_ALIGN_EDIT: Mouse pointer used to edit widget alignment + * + * Indicates what the pointer is used for in the workspace. + */ +typedef enum +{ + GLADE_POINTER_SELECT = 0, + GLADE_POINTER_ADD_WIDGET, + GLADE_POINTER_DRAG_RESIZE, + GLADE_POINTER_MARGIN_EDIT, + GLADE_POINTER_ALIGN_EDIT +} GladePointerMode; + +typedef enum +{ + GLADE_SUPPORT_OK = 0, + GLADE_SUPPORT_DEPRECATED = (0x01 << 0), + GLADE_SUPPORT_MISMATCH = (0x01 << 1) +} GladeSupportMask; + +/** + * GladeProjectModelColumns: + * @GLADE_PROJECT_MODEL_COLUMN_ICON_NAME: name of the icon for the widget + * @GLADE_PROJECT_MODEL_COLUMN_NAME: Name of the widget + * @GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME: The type name of the widget + * @GLADE_PROJECT_MODEL_COLUMN_OBJECT: the GObject of the widget + * @GLADE_PROJECT_MODEL_COLUMN_MISC: the auxilary text describing a widget's role + * @GLADE_PROJECT_MODEL_COLUMN_WARNING: the support warning text for this widget + * @GLADE_PROJECT_MODEL_N_COLUMNS: Number of columns + * + * The tree view columns provided by the GtkTreeModel implemented + * by GladeProject + * + **/ +typedef enum +{ + GLADE_PROJECT_MODEL_COLUMN_ICON_NAME, + GLADE_PROJECT_MODEL_COLUMN_NAME, + GLADE_PROJECT_MODEL_COLUMN_TYPE_NAME, + GLADE_PROJECT_MODEL_COLUMN_OBJECT, + GLADE_PROJECT_MODEL_COLUMN_MISC, + GLADE_PROJECT_MODEL_COLUMN_WARNING, + GLADE_PROJECT_MODEL_N_COLUMNS +} GladeProjectModelColumns; + +/** + * GladeVerifyFlags: + * @GLADE_VERIFY_NONE: No verification + * @GLADE_VERIFY_VERSIONS: Verify version mismatches + * @GLADE_VERIFY_DEPRECATIONS: Verify deprecations + * @GLADE_VERIFY_UNRECOGNIZED: Verify unrecognized types + * + */ +typedef enum { + GLADE_VERIFY_NONE = 0, + GLADE_VERIFY_VERSIONS = (1 << 0), + GLADE_VERIFY_DEPRECATIONS = (1 << 1), + GLADE_VERIFY_UNRECOGNIZED = (1 << 2) +} GladeVerifyFlags; + +struct _GladeProject +{ + GObject parent_instance; + + GladeProjectPrivate *priv; +}; + +struct _GladeProjectClass +{ + GObjectClass parent_class; + + void (*add_object) (GladeProject *project, + GladeWidget *object); + void (*remove_object) (GladeProject *project, + GladeWidget *object); + + void (*undo) (GladeProject *project); + void (*redo) (GladeProject *project); + GladeCommand *(*next_undo_item) (GladeProject *project); + GladeCommand *(*next_redo_item) (GladeProject *project); + void (*push_undo) (GladeProject *project, + GladeCommand *cmd); + + void (*changed) (GladeProject *project, + GladeCommand *command, + gboolean forward); + + void (*widget_name_changed) (GladeProject *project, + GladeWidget *widget); + void (*selection_changed) (GladeProject *project); + void (*close) (GladeProject *project); + + void (*parse_finished) (GladeProject *project); + + void (* glade_reserved1) (void); + void (* glade_reserved2) (void); + void (* glade_reserved3) (void); + void (* glade_reserved4) (void); + void (* glade_reserved5) (void); + void (* glade_reserved6) (void); + void (* glade_reserved7) (void); + void (* glade_reserved8) (void); +}; + + +GType glade_project_get_type (void) G_GNUC_CONST; + +GladeProject *glade_project_new (void); +GladeProject *glade_project_load (const gchar *path); +gboolean glade_project_load_from_file (GladeProject *project, + const gchar *path); +gboolean glade_project_save (GladeProject *project, + const gchar *path, + GError **error); +gboolean glade_project_save_verify (GladeProject *project, + const gchar *path, + GladeVerifyFlags flags, + GError **error); +gboolean glade_project_autosave (GladeProject *project, + GError **error); +gboolean glade_project_backup (GladeProject *project, + const gchar *path, + GError **error); +void glade_project_push_progress (GladeProject *project); +gboolean glade_project_load_cancelled (GladeProject *project); +void glade_project_cancel_load (GladeProject *project); + +void glade_project_preview (GladeProject *project, + GladeWidget *gwidget); +void glade_project_properties (GladeProject *project); +gchar *glade_project_resource_fullpath (GladeProject *project, + const gchar *resource); +void glade_project_set_resource_path (GladeProject *project, + const gchar *path); +const gchar *glade_project_get_resource_path (GladeProject *project); + +void glade_project_widget_visibility_changed (GladeProject *project, + GladeWidget *widget, + gboolean visible); +void glade_project_check_reordered (GladeProject *project, + GladeWidget *parent, + GList *old_order); + +void glade_project_set_template (GladeProject *project, + GladeWidget *widget); +GladeWidget *glade_project_get_template (GladeProject *project); + +void glade_project_set_license (GladeProject *project, + const gchar *license); + +const gchar *glade_project_get_license (GladeProject *project); + +/* Commands */ +void glade_project_undo (GladeProject *project); +void glade_project_redo (GladeProject *project); +GladeCommand *glade_project_next_undo_item (GladeProject *project); +GladeCommand *glade_project_next_redo_item (GladeProject *project); +void glade_project_push_undo (GladeProject *project, + GladeCommand *cmd); +GtkWidget *glade_project_undo_items (GladeProject *project); +GtkWidget *glade_project_redo_items (GladeProject *project); + +/* Add/Remove Objects */ +const GList *glade_project_get_objects (GladeProject *project); +void glade_project_add_object (GladeProject *project, + GObject *object); +void glade_project_remove_object (GladeProject *project, + GObject *object); +gboolean glade_project_has_object (GladeProject *project, + GObject *object); +void glade_project_widget_changed (GladeProject *project, + GladeWidget *gwidget); + +/* Widget names */ +GladeWidget *glade_project_get_widget_by_name (GladeProject *project, + const gchar *name); +void glade_project_set_widget_name (GladeProject *project, + GladeWidget *widget, + const gchar *name); +gchar *glade_project_new_widget_name (GladeProject *project, + GladeWidget *widget, + const gchar *base_name); +gboolean glade_project_available_widget_name(GladeProject *project, + GladeWidget *widget, + const gchar *name); + +/* Selection */ +gboolean glade_project_is_selected (GladeProject *project, + GObject *object); +void glade_project_selection_set (GladeProject *project, + GObject *object, + gboolean emit_signal); +void glade_project_selection_add (GladeProject *project, + GObject *object, + gboolean emit_signal); +void glade_project_selection_remove (GladeProject *project, + GObject *object, + gboolean emit_signal); +void glade_project_selection_clear (GladeProject *project, + gboolean emit_signal); +void glade_project_selection_changed (GladeProject *project); +void glade_project_queue_selection_changed (GladeProject *project); +GList *glade_project_selection_get (GladeProject *project); +gboolean glade_project_get_has_selection (GladeProject *project); + +/* Accessors */ +const gchar *glade_project_get_path (GladeProject *project); +gchar *glade_project_get_name (GladeProject *project); +void glade_project_reset_path (GladeProject *project); +gboolean glade_project_is_loading (GladeProject *project); +time_t glade_project_get_file_mtime (GladeProject *project); +gboolean glade_project_get_readonly (GladeProject *project); +gboolean glade_project_get_modified (GladeProject *project); +void glade_project_set_pointer_mode (GladeProject *project, + GladePointerMode mode); +GladePointerMode glade_project_get_pointer_mode (GladeProject *project); +void glade_project_set_add_item (GladeProject *project, + GladeWidgetAdaptor *adaptor); +GladeWidgetAdaptor *glade_project_get_add_item (GladeProject *project); +void glade_project_set_target_version (GladeProject *project, + const gchar *catalog, + gint major, + gint minor); +void glade_project_get_target_version (GladeProject *project, + const gchar *catalog, + gint *major, + gint *minor); +GList *glade_project_required_libs (GladeProject *project); +gchar *glade_project_display_dependencies (GladeProject *project); + +GList *glade_project_toplevels (GladeProject *project); + +void glade_project_set_translation_domain (GladeProject *project, + const gchar *domain); +const gchar *glade_project_get_translation_domain (GladeProject *project); + +void glade_project_set_css_provider_path (GladeProject *project, + const gchar *path); + +const gchar *glade_project_get_css_provider_path (GladeProject *project); + +/* Verifications */ +gboolean glade_project_verify (GladeProject *project, + gboolean saving, + GladeVerifyFlags flags); +gchar *glade_project_verify_widget_adaptor(GladeProject *project, + GladeWidgetAdaptor *adaptor, + GladeSupportMask *mask); +void glade_project_verify_property (GladeProperty *property); +void glade_project_verify_signal (GladeWidget *widget, + GladeSignal *signal); +gboolean glade_project_writing_preview (GladeProject *project); + +/* General selection driven commands */ +void glade_project_copy_selection (GladeProject *project); +void glade_project_command_cut (GladeProject *project); +void glade_project_command_paste (GladeProject *project, + GladePlaceholder *placeholder); +void glade_project_command_delete (GladeProject *project); + +G_END_DECLS + +#endif /* __GLADE_PROJECT_H__ */ diff --git a/gladeui/glade-property-def.c b/gladeui/glade-property-def.c new file mode 100644 index 0000000..c3ca595 --- /dev/null +++ b/gladeui/glade-property-def.c @@ -0,0 +1,2443 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Chema Celorio + * Tristan Van Berkom + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/** + * SECTION:glade-property-def + * @Title: GladePropertyDef + * @Short_Description: Property Definition metadata. + * + * #GladePropertyDef is a structure based on a #GParamSpec and parameters + * from the Glade catalog files and describes how properties are to be handled + * in Glade; it also provides an interface to convert #GValue to strings and + * va_lists etc (back and forth). + */ + +#include +#include +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-property.h" +#include "glade-property-def.h" +#include "glade-editor-property.h" +#include "glade-displayable-values.h" +#include "glade-debug.h" + +#define NUMERICAL_STEP_INCREMENT 1.0F +#define NUMERICAL_PAGE_INCREMENT 10.0F +#define NUMERICAL_PAGE_SIZE 0.0F + +#define FLOATING_STEP_INCREMENT 0.01F +#define FLOATING_PAGE_INCREMENT 0.1F +#define FLOATING_PAGE_SIZE 0.00F + + +struct _GladePropertyDef +{ + GladeWidgetAdaptor *adaptor; /* The GladeWidgetAdaptor that this property class + * was created for. + */ + + guint16 version_since_major; /* Version in which this property was */ + guint16 version_since_minor; /* introduced. */ + + GParamSpec *pspec; /* The Parameter Specification for this property. + */ + + gchar *id; /* The id of the property. Like "label" or "xpad" + * this is a non-translatable string + */ + + gchar *name; /* The name of the property. Like "Label" or "X Pad" + * this is a translatable string + */ + + gchar *tooltip; /* The default tooltip for the property editor rows. + */ + + GValue *def; /* The default value for this property (this will exist + * as a copy of orig_def if not specified by the catalog) + */ + + GValue *orig_def; /* The real default value obtained through introspection. + * (used to decide whether we should write to the + * glade file or not, or to restore the loaded property + * correctly); all property classes have and orig_def. + */ + + guint multiline : 1; /* Whether to use multiple lines to edit this text property. + */ + + guint virt : 1; /* Whether this is a virtual property with its pspec supplied + * via the catalog (or hard code-paths); or FALSE if its a real + * GObject introspected property + */ + + guint optional : 1; /* Some properties are optional by nature like + * default width. It can be set or not set. A + * default property has a check box in the + * left that enables/disables the input + */ + + guint optional_default : 1; /* For optional values, what the default is */ + + guint construct_only : 1; /* Whether this property is G_PARAM_CONSTRUCT_ONLY or not */ + + guint common : 1; /* Common properties go in the common tab */ + guint atk : 1; /* Atk properties go in the atk tab */ + guint packing : 1; /* Packing properties go in the packing tab */ + guint query : 1; /* Whether we should explicitly ask the user about this property + * when instantiating a widget with this property (through a popup + * dialog). + */ + + guint translatable : 1; /* The property should be translatable, which + * means that it needs extra parameters in the + * UI. + */ + + /* These three are the master switches for the glade-file output, + * property editor availability & live object updates in the glade environment. + */ + guint save : 1; /* Whether we should save to the glade file or not + * (mostly just for virtual internal glade properties, + * also used for properties with generic pspecs that + * are saved in custom ways by the plugin) + */ + guint save_always : 1; /* Used to make a special case exception and always + * save this property regardless of what the default + * value is (used for some special cases like properties + * that are assigned initial values in composite widgets + * or derived widget code). + */ + guint visible : 1; /* Whether or not to show this property in the editor & + * reset dialog. + */ + + guint custom_layout : 1; /* Properties marked as custom_layout will not be included + * in a base #GladeEditorTable implementation (use this + * for properties you want to layout in custom ways in + * a #GladeEditable widget + */ + + guint ignore : 1; /* When true, we will not sync the object when the property + * changes, or load values from the object. + */ + + guint needs_sync : 1; /* Virtual properties need to be synchronized after object + * creation, some properties that are not virtual also need + * handling from the backend, if "needs-sync" is true then + * this property will by synced with virtual properties. + */ + + guint is_modified : 1; /* If true, this property_def has been "modified" from the + * the standard property by a xml file. */ + + guint themed_icon : 1; /* Some GParamSpecString properties refer to icon names + * in the icon theme... these need to be specified in the + * property class definition if proper editing tools are to + * be used. + */ + guint stock_icon : 1; /* String properties can also denote stock icons, including + * icons from icon factories... + */ + guint stock : 1; /* ... or a narrower list of "items" from gtk builtin stock items. + */ + + guint transfer_on_paste : 1; /* If this is a packing prop, + * whether we should transfer it on paste. + */ + + guint parentless_widget : 1; /* True if this property should point to a parentless widget + * in the project + */ + + guint deprecated : 1; /* True if this property is deprecated */ + + guint16 deprecated_since_major; + guint16 deprecated_since_minor; + + gdouble weight; /* This will determine the position of this property in + * the editor. + */ + + gchar *create_type; /* If this is an object property and you want the option to create + * one from the object selection dialog, then set the name of the + * concrete type here. + */ +}; + +G_DEFINE_BOXED_TYPE (GladePropertyDef, glade_property_def, glade_property_def_clone, glade_property_def_free) + +/** + * glade_property_def_new: + * @adaptor: The #GladeWidgetAdaptor to create this property for + * @id: the id for the new property class + * + * Returns: a new #GladePropertyDef + */ +GladePropertyDef * +glade_property_def_new (GladeWidgetAdaptor *adaptor, + const gchar *id) +{ + GladePropertyDef *property_def; + + property_def = g_slice_new0 (GladePropertyDef); + property_def->adaptor = adaptor; + property_def->pspec = NULL; + property_def->id = g_strdup (id); + property_def->name = NULL; + property_def->tooltip = NULL; + property_def->def = NULL; + property_def->orig_def = NULL; + property_def->query = FALSE; + property_def->optional = FALSE; + property_def->optional_default = FALSE; + property_def->is_modified = FALSE; + property_def->common = FALSE; + property_def->packing = FALSE; + property_def->atk = FALSE; + property_def->visible = TRUE; + property_def->custom_layout = FALSE; + property_def->save = TRUE; + property_def->save_always = FALSE; + property_def->ignore = FALSE; + property_def->needs_sync = FALSE; + property_def->themed_icon = FALSE; + property_def->stock = FALSE; + property_def->stock_icon = FALSE; + property_def->translatable = FALSE; + property_def->virt = TRUE; + property_def->transfer_on_paste = FALSE; + property_def->weight = -1.0; + property_def->parentless_widget = FALSE; + property_def->create_type = NULL; + + /* Initialize property versions & deprecated to adaptor */ + property_def->version_since_major = GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MAJOR (adaptor); + property_def->version_since_minor = GLADE_WIDGET_ADAPTOR_VERSION_SINCE_MINOR (adaptor); + property_def->deprecated = GLADE_WIDGET_ADAPTOR_DEPRECATED (adaptor); + property_def->deprecated_since_major = 0; + property_def->deprecated_since_minor = 0; + + return property_def; +} + +/** + * glade_property_def_clone: + * @property_def: a #GladePropertyDef + * + * Returns: (transfer full): a new #GladePropertyDef cloned from @property_def + */ +GladePropertyDef * +glade_property_def_clone (GladePropertyDef *property_def) +{ + GladePropertyDef *clone; + + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + clone = g_new0 (GladePropertyDef, 1); + + /* copy ints over */ + memcpy (clone, property_def, sizeof (GladePropertyDef)); + + /* Make sure we own our strings */ + clone->pspec = property_def->pspec; + clone->id = g_strdup (clone->id); + clone->name = g_strdup (clone->name); + clone->tooltip = g_strdup (clone->tooltip); + + if (G_IS_VALUE (property_def->def)) + { + clone->def = g_new0 (GValue, 1); + g_value_init (clone->def, property_def->pspec->value_type); + g_value_copy (property_def->def, clone->def); + } + + if (G_IS_VALUE (property_def->orig_def)) + { + clone->orig_def = g_new0 (GValue, 1); + g_value_init (clone->orig_def, property_def->pspec->value_type); + g_value_copy (property_def->orig_def, clone->orig_def); + } + + if (property_def->create_type) + clone->create_type = g_strdup (property_def->create_type); + + return clone; +} + +/** + * glade_property_def_free: + * @property_def: a #GladePropertyDef + * + * Frees @property_def and its associated memory. + */ +void +glade_property_def_free (GladePropertyDef *property_def) +{ + if (property_def == NULL) + return; + + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + g_clear_pointer (&property_def->id, g_free); + g_clear_pointer (&property_def->name, g_free); + g_clear_pointer (&property_def->tooltip, g_free); + if (property_def->orig_def) + { + if (G_VALUE_TYPE (property_def->orig_def) != 0) + g_value_unset (property_def->orig_def); + g_clear_pointer (&property_def->orig_def, g_free); + } + if (property_def->def) + { + if (G_VALUE_TYPE (property_def->def) != 0) + g_value_unset (property_def->def); + g_clear_pointer (&property_def->def, g_free); + } + + g_clear_pointer (&property_def->create_type, g_free); + g_slice_free (GladePropertyDef, property_def); +} + + +GValue * +glade_property_def_get_default_from_spec (GParamSpec * spec) +{ + GValue *value; + value = g_new0 (GValue, 1); + g_value_init (value, spec->value_type); + g_param_value_set_default (spec, value); + return value; +} + + +static gchar * +glade_property_def_make_string_from_enum (GType etype, gint eval) +{ + GEnumClass *eclass; + gchar *string = NULL; + guint i; + + g_return_val_if_fail ((eclass = g_type_class_ref (etype)) != NULL, NULL); + for (i = 0; i < eclass->n_values; i++) + { + if (eval == eclass->values[i].value) + { + string = g_strdup (eclass->values[i].value_nick); + break; + } + } + g_type_class_unref (eclass); + return string; +} + +static gchar * +glade_property_def_make_string_from_flags (GladePropertyDef * property_def, + guint fvals, gboolean displayables) +{ + GFlagsClass *fclass; + GFlagsValue *fvalue; + GString *string; + gchar *retval; + + g_return_val_if_fail ((fclass = + g_type_class_ref (property_def->pspec->value_type)) != NULL, + NULL); + + string = g_string_new (""); + + while ((fvalue = g_flags_get_first_value (fclass, fvals)) != NULL) + { + const gchar *val_str = NULL; + + fvals &= ~fvalue->value; + + if (displayables) + val_str = glade_get_displayable_value (property_def->pspec->value_type, + fvalue->value_name); + + if (string->str[0]) + g_string_append (string, " | "); + + g_string_append (string, (val_str) ? val_str : fvalue->value_name); + + /* If one of the flags value is 0 this loop become infinite :) */ + if (fvalue->value == 0) + break; + } + + retval = string->str; + + g_type_class_unref (fclass); + g_string_free (string, FALSE); + + return retval; +} + +static gchar * +glade_property_def_make_string_from_object (GladePropertyDef *property_def, + GObject *object) +{ + GladeWidget *gwidget; + gchar *string = NULL, *filename; + + if (!object) + return NULL; + + if (property_def->pspec->value_type == GDK_TYPE_PIXBUF) + { + if ((filename = g_object_get_data (object, "GladeFileName")) != NULL) + string = g_strdup (filename); + } + else if (property_def->pspec->value_type == G_TYPE_FILE) + { + if ((filename = g_object_get_data (object, "GladeFileURI")) != NULL) + string = g_strdup (filename); + } + else if ((gwidget = glade_widget_get_from_gobject (object)) != NULL) + string = g_strdup (glade_widget_get_name (gwidget)); + else + g_critical ("Object type property refers to an object " + "outside the project"); + + return string; +} + +static gchar * +glade_property_def_make_string_from_objects (GladePropertyDef * + property_def, GList * objects) +{ + GObject *object; + GList *list; + gchar *string = NULL, *obj_str, *tmp; + + for (list = objects; list; list = list->next) + { + object = list->data; + + obj_str = + glade_property_def_make_string_from_object (property_def, object); + + if (string == NULL) + string = obj_str; + else if (obj_str != NULL) + { + tmp = + g_strdup_printf ("%s%s%s", string, GLADE_PROPERTY_DEF_OBJECT_DELIMITER, obj_str); + string = (g_free (string), tmp); + g_free (obj_str); + } + } + return string; +} + +static gchar * +glade_dtostr (double number, gdouble epsilon) +{ + char *str = g_malloc (G_ASCII_DTOSTR_BUF_SIZE + 1); + char real_number[G_ASCII_DTOSTR_BUF_SIZE + 1]; + const gchar *decimal = NULL; + int i; + + g_ascii_dtostr (str, G_ASCII_DTOSTR_BUF_SIZE, number); + g_ascii_dtostr (real_number, G_ASCII_DTOSTR_BUF_SIZE, number); + decimal = g_strstr_len (real_number, G_ASCII_DTOSTR_BUF_SIZE, "."); + + if (!decimal) + return str; + + decimal++; + + for (i = 1; i <= 20; i++) + { + gint len = (decimal - real_number) + i; + double rounded; + + /* add up to i decimal points */ + str[len] = real_number[len]; + str[len+1] = '\0'; + + rounded = g_ascii_strtod (str, NULL); + + if (ABS (rounded - number) <= epsilon) + return str; + } + + return str; +} + +/** + * glade_property_def_make_string_from_gvalue: + * @property_def: A #GladePropertyDef + * @value: A #GValue + * + * Returns: A newly allocated string representation of @value + */ +gchar * +glade_property_def_make_string_from_gvalue (GladePropertyDef *property_def, + const GValue *value) +{ + gchar *string = NULL; + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + if (GLADE_PROPERTY_IS_PARAM_SPEC_VALUE_ARRAY (property_def->pspec)) + { + G_GNUC_END_IGNORE_DEPRECATIONS; + GValueArray *value_array = g_value_get_boxed (value); + + if (value_array && value_array->n_values && + G_VALUE_HOLDS (&value_array->values[0], G_TYPE_STRING)) + { + gint i, n_values = value_array->n_values; + GString *gstring = g_string_new (NULL); + + for (i = 0; i < n_values; i++) + { + g_string_append (gstring, + g_value_get_string (&value_array->values[i])); + g_string_append_c (gstring, '\n'); + } + string = gstring->str; + g_string_free (gstring, FALSE); + } + } + else if (G_IS_PARAM_SPEC_ENUM (property_def->pspec)) + { + gint eval = g_value_get_enum (value); + string = glade_property_def_make_string_from_enum + (property_def->pspec->value_type, eval); + } + else if (G_IS_PARAM_SPEC_FLAGS (property_def->pspec)) + { + guint flags = g_value_get_flags (value); + string = glade_property_def_make_string_from_flags + (property_def, flags, FALSE); + } + else if (G_IS_PARAM_SPEC_BOXED (property_def->pspec)) + { + if (property_def->pspec->value_type == GDK_TYPE_COLOR) + { + GdkColor *color = g_value_get_boxed (value); + if (color) + string = g_strdup_printf ("#%04x%04x%04x", + color->red, color->green, color->blue); + } + else if (property_def->pspec->value_type == GDK_TYPE_RGBA) + { + GdkRGBA *rgba = g_value_get_boxed (value); + if (rgba) + string = gdk_rgba_to_string (rgba); + } + else if (property_def->pspec->value_type == PANGO_TYPE_COLOR) + { + PangoColor *color = g_value_get_boxed (value); + if (color) + string = pango_color_to_string (color); + } + else if (property_def->pspec->value_type == G_TYPE_STRV) + { + gchar **strv = g_value_get_boxed (value); + if (strv) + string = g_strjoinv ("\n", strv); + } + } + else if (G_IS_PARAM_SPEC_INT (property_def->pspec)) + string = g_strdup_printf ("%d", g_value_get_int (value)); + else if (G_IS_PARAM_SPEC_UINT (property_def->pspec)) + string = g_strdup_printf ("%u", g_value_get_uint (value)); + else if (G_IS_PARAM_SPEC_LONG (property_def->pspec)) + string = g_strdup_printf ("%ld", g_value_get_long (value)); + else if (G_IS_PARAM_SPEC_ULONG (property_def->pspec)) + string = g_strdup_printf ("%lu", g_value_get_ulong (value)); + else if (G_IS_PARAM_SPEC_INT64 (property_def->pspec)) + string = g_strdup_printf ("%" G_GINT64_FORMAT, g_value_get_int64 (value)); + else if (G_IS_PARAM_SPEC_UINT64 (property_def->pspec)) + string = g_strdup_printf ("%" G_GUINT64_FORMAT, g_value_get_uint64 (value)); + else if (G_IS_PARAM_SPEC_FLOAT (property_def->pspec)) + string = glade_dtostr (g_value_get_float (value), + ((GParamSpecFloat *)property_def->pspec)->epsilon); + else if (G_IS_PARAM_SPEC_DOUBLE (property_def->pspec)) + string = glade_dtostr (g_value_get_double (value), + ((GParamSpecDouble *)property_def->pspec)->epsilon); + else if (G_IS_PARAM_SPEC_STRING (property_def->pspec)) + { + string = g_value_dup_string (value); + } + else if (G_IS_PARAM_SPEC_CHAR (property_def->pspec)) + string = g_strdup_printf ("%c", g_value_get_schar (value)); + else if (G_IS_PARAM_SPEC_UCHAR (property_def->pspec)) + string = g_strdup_printf ("%c", g_value_get_uchar (value)); + else if (G_IS_PARAM_SPEC_UNICHAR (property_def->pspec)) + { + int len; + string = g_malloc (7); + len = g_unichar_to_utf8 (g_value_get_uint (value), string); + string[len] = '\0'; + } + else if (G_IS_PARAM_SPEC_BOOLEAN (property_def->pspec)) + string = g_strdup_printf ("%s", g_value_get_boolean (value) ? + GLADE_TAG_TRUE : GLADE_TAG_FALSE); + else if (G_IS_PARAM_SPEC_VARIANT (property_def->pspec)) + { + GVariant *variant; + + if ((variant = g_value_get_variant (value))) + string = g_variant_print (variant, TRUE); + } + else if (G_IS_PARAM_SPEC_OBJECT (property_def->pspec)) + { + GObject *object = g_value_get_object (value); + string = + glade_property_def_make_string_from_object (property_def, object); + } + else if (GLADE_IS_PARAM_SPEC_OBJECTS (property_def->pspec)) + { + GList *objects = g_value_get_boxed (value); + string = + glade_property_def_make_string_from_objects (property_def, + objects); + } + else + g_critical ("Unsupported pspec type %s (value -> string)", + g_type_name (G_PARAM_SPEC_TYPE (property_def->pspec))); + + return string; +} + +/* This is copied exactly from libglade. I've just renamed the function. + */ +guint +glade_property_def_make_flags_from_string (GType type, const char *string) +{ + GFlagsClass *fclass; + gchar *endptr, *prevptr; + guint i, j, ret = 0; + char *flagstr; + + ret = strtoul (string, &endptr, 0); + if (endptr != string) /* parsed a number */ + return ret; + + fclass = g_type_class_ref (type); + + + flagstr = g_strdup (string); + for (ret = i = j = 0;; i++) + { + gboolean eos; + + eos = flagstr[i] == '\0'; + + if (eos || flagstr[i] == '|') + { + GFlagsValue *fv; + const char *flag; + gunichar ch; + + flag = &flagstr[j]; + endptr = &flagstr[i]; + + if (!eos) + { + flagstr[i++] = '\0'; + j = i; + } + + /* trim spaces */ + for (;;) + { + ch = g_utf8_get_char (flag); + if (!g_unichar_isspace (ch)) + break; + flag = g_utf8_next_char (flag); + } + + while (endptr > flag) + { + prevptr = g_utf8_prev_char (endptr); + ch = g_utf8_get_char (prevptr); + if (!g_unichar_isspace (ch)) + break; + endptr = prevptr; + } + + if (endptr > flag) + { + *endptr = '\0'; + fv = g_flags_get_value_by_name (fclass, flag); + + if (!fv) + fv = g_flags_get_value_by_nick (fclass, flag); + + if (fv) + ret |= fv->value; + else + g_warning ("Unknown flag: '%s'", flag); + } + + if (eos) + break; + } + } + + g_free (flagstr); + + g_type_class_unref (fclass); + + return ret; +} + +/* This is copied exactly from libglade. I've just renamed the function. + */ +static gint +glade_property_def_make_enum_from_string (GType type, const char *string) +{ + GEnumClass *eclass; + GEnumValue *ev; + gchar *endptr; + gint ret = 0; + + ret = strtoul (string, &endptr, 0); + if (endptr != string) /* parsed a number */ + return ret; + + eclass = g_type_class_ref (type); + ev = g_enum_get_value_by_name (eclass, string); + if (!ev) + ev = g_enum_get_value_by_nick (eclass, string); + if (ev) + ret = ev->value; + + g_type_class_unref (eclass); + + return ret; +} + +static GObject * +glade_property_def_make_object_from_string (GladePropertyDef * + property_def, + const gchar * string, + GladeProject * project) +{ + GObject *object = NULL; + + if (string == NULL || project == NULL) + return NULL; + + if (property_def->pspec->value_type == GDK_TYPE_PIXBUF) + { + g_autofree gchar *fullpath = NULL; + GdkPixbuf *pixbuf; + + if (*string == '\0') + return NULL; + + if (g_str_has_prefix (string, "resource:///")) + fullpath = glade_project_resource_fullpath (project, &string[11]); + else + fullpath = glade_project_resource_fullpath (project, string); + + if ((pixbuf = gdk_pixbuf_new_from_file (fullpath, NULL)) == NULL) + { + GdkPixbuf *icon = gtk_icon_theme_load_icon (gtk_icon_theme_get_default (), + "image-missing", 22, 0, NULL); + /* Use a copy, since gtk_icon_theme_load_icon() returns the same pixbuf */ + pixbuf = gdk_pixbuf_copy (icon); + g_object_unref (icon); + } + + if (pixbuf) + { + object = G_OBJECT (pixbuf); + g_object_set_data_full (object, "GladeFileName", + g_strdup (string), g_free); + } + } + else if (property_def->pspec->value_type == G_TYPE_FILE) + { + GFile *file; + + if (*string == '\0') + return NULL; + + file = g_file_new_for_uri (string); + + object = G_OBJECT (file); + g_object_set_data_full (object, "GladeFileURI", + g_strdup (string), g_free); + } + else + { + GladeWidget *gwidget; + if ((gwidget = glade_project_get_widget_by_name (project, string)) != NULL) + object = glade_widget_get_object (gwidget); + } + + return object; +} + +static GList * +glade_property_def_make_objects_from_string (GladePropertyDef * + property_def, + const gchar * string, + GladeProject * project) +{ + GList *objects = NULL; + GObject *object; + gchar **split; + guint i; + + if ((split = g_strsplit (string, GLADE_PROPERTY_DEF_OBJECT_DELIMITER, 0)) != NULL) + { + for (i = 0; split[i]; i++) + { + if ((object = + glade_property_def_make_object_from_string (property_def, + split[i], + project)) != NULL) + objects = g_list_prepend (objects, object); + } + g_strfreev (split); + } + return g_list_reverse (objects); +} + +/** + * glade_property_def_make_gvalue_from_string: + * @property_def: A #GladePropertyDef + * @string: a string representation of this property + * @project: the #GladeProject that the property should be resolved for + * + * Returns: A #GValue created based on the @property_def + * and @string criteria. + */ +GValue * +glade_property_def_make_gvalue_from_string (GladePropertyDef *property_def, + const gchar *string, + GladeProject *project) +{ + GValue *value = g_new0 (GValue, 1); + gchar **strv; + + g_value_init (value, property_def->pspec->value_type); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS + if (GLADE_PROPERTY_IS_PARAM_SPEC_VALUE_ARRAY (property_def->pspec)) + { + G_GNUC_END_IGNORE_DEPRECATIONS; + GValueArray *value_array; + GValue str_value = { 0, }; + gint i; + + /* Require deprecated code */ + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + value_array = g_value_array_new (0); + G_GNUC_END_IGNORE_DEPRECATIONS; + + g_value_init (&str_value, G_TYPE_STRING); + strv = g_strsplit (string, "\n", 0); + + for (i = 0; strv[i]; i++) + { + g_value_set_static_string (&str_value, strv[i]); + + G_GNUC_BEGIN_IGNORE_DEPRECATIONS; + value_array = g_value_array_append (value_array, &str_value); + G_GNUC_END_IGNORE_DEPRECATIONS; + } + g_value_take_boxed (value, value_array); + g_strfreev (strv); + } + else if (G_IS_PARAM_SPEC_ENUM (property_def->pspec)) + { + gint eval = glade_property_def_make_enum_from_string + (property_def->pspec->value_type, string); + g_value_set_enum (value, eval); + } + else if (G_IS_PARAM_SPEC_FLAGS (property_def->pspec)) + { + guint flags = glade_property_def_make_flags_from_string + (property_def->pspec->value_type, string); + g_value_set_flags (value, flags); + } + else if (G_IS_PARAM_SPEC_BOXED (property_def->pspec)) + { + if (property_def->pspec->value_type == GDK_TYPE_COLOR) + { + GdkColor color; + if (gdk_color_parse (string, &color)) + g_value_set_boxed (value, &color); + else + g_warning ("could not parse colour name `%s'", string); + } + else if (property_def->pspec->value_type == GDK_TYPE_RGBA) + { + GdkRGBA rgba; + if (gdk_rgba_parse (&rgba, string)) + g_value_set_boxed (value, &rgba); + else + g_warning ("could not parse rgba colour name `%s'", string); + } + else if (property_def->pspec->value_type == PANGO_TYPE_COLOR) + { + PangoColor color; + if (pango_color_parse (&color, string)) + g_value_set_boxed (value, &color); + else + g_warning ("could not parse pango color name `%s'", string); + } + else if (property_def->pspec->value_type == G_TYPE_STRV) + { + strv = g_strsplit (string, "\n", 0); + g_value_take_boxed (value, strv); + } + } + else if (G_IS_PARAM_SPEC_VARIANT (property_def->pspec)) + g_value_take_variant (value, g_variant_parse (NULL, string, NULL, NULL, NULL)); + else if (G_IS_PARAM_SPEC_INT (property_def->pspec)) + g_value_set_int (value, g_ascii_strtoll (string, NULL, 10)); + else if (G_IS_PARAM_SPEC_UINT (property_def->pspec)) + g_value_set_uint (value, g_ascii_strtoull (string, NULL, 10)); + else if (G_IS_PARAM_SPEC_LONG (property_def->pspec)) + g_value_set_long (value, g_ascii_strtoll (string, NULL, 10)); + else if (G_IS_PARAM_SPEC_ULONG (property_def->pspec)) + g_value_set_ulong (value, g_ascii_strtoull (string, NULL, 10)); + else if (G_IS_PARAM_SPEC_INT64 (property_def->pspec)) + g_value_set_int64 (value, g_ascii_strtoll (string, NULL, 10)); + else if (G_IS_PARAM_SPEC_UINT64 (property_def->pspec)) + g_value_set_uint64 (value, g_ascii_strtoull (string, NULL, 10)); + else if (G_IS_PARAM_SPEC_FLOAT (property_def->pspec)) + g_value_set_float (value, (float) g_ascii_strtod (string, NULL)); + else if (G_IS_PARAM_SPEC_DOUBLE (property_def->pspec)) + g_value_set_double (value, g_ascii_strtod (string, NULL)); + else if (G_IS_PARAM_SPEC_STRING (property_def->pspec)) + g_value_set_string (value, string); + else if (G_IS_PARAM_SPEC_CHAR (property_def->pspec)) + g_value_set_schar (value, string[0]); + else if (G_IS_PARAM_SPEC_UCHAR (property_def->pspec)) + g_value_set_uchar (value, string[0]); + else if (G_IS_PARAM_SPEC_UNICHAR (property_def->pspec)) + g_value_set_uint (value, g_utf8_get_char (string)); + else if (G_IS_PARAM_SPEC_BOOLEAN (property_def->pspec)) + { + gboolean val; + if (glade_utils_boolean_from_string (string, &val)) + g_value_set_boolean (value, FALSE); + else + g_value_set_boolean (value, val); + + } + else if (G_IS_PARAM_SPEC_OBJECT (property_def->pspec)) + { + GObject *object = + glade_property_def_make_object_from_string (property_def, string, project); + g_value_set_object (value, object); + } + else if (GLADE_IS_PARAM_SPEC_OBJECTS (property_def->pspec)) + { + GList *objects = + glade_property_def_make_objects_from_string (property_def, string, project); + g_value_take_boxed (value, objects); + } + else + g_critical ("Unsupported pspec type %s (string -> value)", + g_type_name (G_PARAM_SPEC_TYPE (property_def->pspec))); + + return value; +} + +/** + * glade_property_def_make_gvalue_from_vl: + * @property_def: A #GladePropertyDef + * @vl: a #va_list holding one argument of the correct type + * specified by @property_def + * + * Returns: A #GValue created based on the @property_def + * and a @vl arg of the correct type. + */ +GValue * +glade_property_def_make_gvalue_from_vl (GladePropertyDef *property_def, + va_list vl) +{ + GValue *value; + + g_return_val_if_fail (property_def != NULL, NULL); + + value = g_new0 (GValue, 1); + g_value_init (value, property_def->pspec->value_type); + + if (G_IS_PARAM_SPEC_ENUM (property_def->pspec)) + g_value_set_enum (value, va_arg (vl, gint)); + else if (G_IS_PARAM_SPEC_FLAGS (property_def->pspec)) + g_value_set_flags (value, va_arg (vl, gint)); + else if (G_IS_PARAM_SPEC_INT (property_def->pspec)) + g_value_set_int (value, va_arg (vl, gint)); + else if (G_IS_PARAM_SPEC_UINT (property_def->pspec)) + g_value_set_uint (value, va_arg (vl, guint)); + else if (G_IS_PARAM_SPEC_LONG (property_def->pspec)) + g_value_set_long (value, va_arg (vl, glong)); + else if (G_IS_PARAM_SPEC_ULONG (property_def->pspec)) + g_value_set_ulong (value, va_arg (vl, gulong)); + else if (G_IS_PARAM_SPEC_INT64 (property_def->pspec)) + g_value_set_int64 (value, va_arg (vl, gint64)); + else if (G_IS_PARAM_SPEC_UINT64 (property_def->pspec)) + g_value_set_uint64 (value, va_arg (vl, guint64)); + else if (G_IS_PARAM_SPEC_FLOAT (property_def->pspec)) + g_value_set_float (value, (gfloat) va_arg (vl, gdouble)); + else if (G_IS_PARAM_SPEC_DOUBLE (property_def->pspec)) + g_value_set_double (value, va_arg (vl, gdouble)); + else if (G_IS_PARAM_SPEC_STRING (property_def->pspec)) + g_value_set_string (value, va_arg (vl, gchar *)); + else if (G_IS_PARAM_SPEC_CHAR (property_def->pspec)) + g_value_set_schar (value, (gchar) va_arg (vl, gint)); + else if (G_IS_PARAM_SPEC_UCHAR (property_def->pspec)) + g_value_set_uchar (value, (guchar) va_arg (vl, guint)); + else if (G_IS_PARAM_SPEC_UNICHAR (property_def->pspec)) + g_value_set_uint (value, va_arg (vl, gunichar)); + else if (G_IS_PARAM_SPEC_BOOLEAN (property_def->pspec)) + g_value_set_boolean (value, va_arg (vl, gboolean)); + else if (G_IS_PARAM_SPEC_OBJECT (property_def->pspec)) + g_value_set_object (value, va_arg (vl, gpointer)); + else if (G_VALUE_HOLDS_BOXED (value)) + g_value_set_boxed (value, va_arg (vl, gpointer)); + else + g_critical ("Unsupported pspec type %s (vl -> string)", + g_type_name (G_PARAM_SPEC_TYPE (property_def->pspec))); + + return value; +} + +/** + * glade_property_def_make_gvalue: + * @property_def: A #GladePropertyDef + * @...: an argument of the correct type specified by @property_def + * + * Returns: A #GValue created based on the @property_def + * and the provided argument. + */ +GValue * +glade_property_def_make_gvalue (GladePropertyDef * property_def, ...) +{ + GValue *value; + va_list vl; + + g_return_val_if_fail (property_def != NULL, NULL); + + va_start (vl, property_def); + value = glade_property_def_make_gvalue_from_vl (property_def, vl); + va_end (vl); + + return value; +} + + +/** + * glade_property_def_set_vl_from_gvalue: + * @property_def: A #GladePropertyDef + * @value: A #GValue to set + * @vl: a #va_list holding one argument of the correct type + * specified by @property_def + * + * + * Sets @vl from @value based on @property_def criteria. + */ +void +glade_property_def_set_vl_from_gvalue (GladePropertyDef * property_def, + GValue * value, va_list vl) +{ + g_return_if_fail (property_def != NULL); + g_return_if_fail (value != NULL); + + /* The argument is a pointer of the specified type, cast the pointer and assign + * the value using the proper g_value_get_ variation. + */ + if (G_IS_PARAM_SPEC_ENUM (property_def->pspec)) + *(gint *) (va_arg (vl, gint *)) = g_value_get_enum (value); + else if (G_IS_PARAM_SPEC_FLAGS (property_def->pspec)) + *(gint *) (va_arg (vl, gint *)) = g_value_get_flags (value); + else if (G_IS_PARAM_SPEC_INT (property_def->pspec)) + *(gint *) (va_arg (vl, gint *)) = g_value_get_int (value); + else if (G_IS_PARAM_SPEC_UINT (property_def->pspec)) + *(guint *) (va_arg (vl, guint *)) = g_value_get_uint (value); + else if (G_IS_PARAM_SPEC_LONG (property_def->pspec)) + *(glong *) (va_arg (vl, glong *)) = g_value_get_long (value); + else if (G_IS_PARAM_SPEC_ULONG (property_def->pspec)) + *(gulong *) (va_arg (vl, gulong *)) = g_value_get_ulong (value); + else if (G_IS_PARAM_SPEC_INT64 (property_def->pspec)) + *(gint64 *) (va_arg (vl, gint64 *)) = g_value_get_int64 (value); + else if (G_IS_PARAM_SPEC_UINT64 (property_def->pspec)) + *(guint64 *) (va_arg (vl, guint64 *)) = g_value_get_uint64 (value); + else if (G_IS_PARAM_SPEC_FLOAT (property_def->pspec)) + *(gfloat *) (va_arg (vl, gdouble *)) = g_value_get_float (value); + else if (G_IS_PARAM_SPEC_DOUBLE (property_def->pspec)) + *(gdouble *) (va_arg (vl, gdouble *)) = g_value_get_double (value); + else if (G_IS_PARAM_SPEC_STRING (property_def->pspec)) + *(gchar **) (va_arg (vl, gchar *)) = (gchar *) g_value_get_string (value); + else if (G_IS_PARAM_SPEC_CHAR (property_def->pspec)) + *(gchar *) (va_arg (vl, gint *)) = g_value_get_schar (value); + else if (G_IS_PARAM_SPEC_UCHAR (property_def->pspec)) + *(guchar *) (va_arg (vl, guint *)) = g_value_get_uchar (value); + else if (G_IS_PARAM_SPEC_UNICHAR (property_def->pspec)) + *(guint *) (va_arg (vl, gunichar *)) = g_value_get_uint (value); + else if (G_IS_PARAM_SPEC_BOOLEAN (property_def->pspec)) + *(gboolean *) (va_arg (vl, gboolean *)) = g_value_get_boolean (value); + else if (G_IS_PARAM_SPEC_OBJECT (property_def->pspec)) + *(gpointer *) (va_arg (vl, gpointer *)) = g_value_get_object (value); + else if (G_VALUE_HOLDS_BOXED (value)) + *(gpointer *) (va_arg (vl, gpointer *)) = g_value_get_boxed (value); + else + g_critical ("Unsupported pspec type %s (string -> vl)", + g_type_name (G_PARAM_SPEC_TYPE (property_def->pspec))); +} + +/** + * glade_property_def_get_from_gvalue: + * @property_def: A #GladePropertyDef + * @value: A #GValue to set + * @...: a return location of the correct type + * + * + * Assignes the provided return location to @value + */ +void +glade_property_def_get_from_gvalue (GladePropertyDef *property_def, + GValue *value, + ...) +{ + va_list vl; + + g_return_if_fail (property_def != NULL); + + va_start (vl, value); + glade_property_def_set_vl_from_gvalue (property_def, value, vl); + va_end (vl); +} + + +/* "need_adaptor": An evil trick to let us create pclasses without + * adaptors and editors. + */ +GladePropertyDef * +glade_property_def_new_from_spec_full (GladeWidgetAdaptor *adaptor, + GParamSpec *spec, + gboolean need_adaptor) +{ + GObjectClass *gtk_widget_class; + GladePropertyDef *property_def; + GladeEditorProperty *eprop = NULL; + + g_return_val_if_fail (spec != NULL, NULL); + gtk_widget_class = g_type_class_ref (GTK_TYPE_WIDGET); + + /* Only properties that are _new_from_spec() are + * not virtual properties + */ + property_def = glade_property_def_new (adaptor, spec->name); + property_def->virt = FALSE; + property_def->pspec = spec; + + /* We only use the writable properties */ + if ((spec->flags & G_PARAM_WRITABLE) == 0) + goto failed; + + property_def->name = g_strdup (g_param_spec_get_nick (spec)); + + /* Register only editable properties. + */ + if (need_adaptor && !(eprop = glade_widget_adaptor_create_eprop + (GLADE_WIDGET_ADAPTOR (adaptor), property_def, FALSE))) + goto failed; + + /* Just created it to see if it was supported.... destroy now... */ + if (eprop) + gtk_widget_destroy (GTK_WIDGET (eprop)); + + /* If its on the GtkWidgetClass, it goes in "common" + * (unless stipulated otherwise in the xml file) + */ + if (g_object_class_find_property (gtk_widget_class, + g_param_spec_get_name (spec)) != NULL) + property_def->common = TRUE; + + /* Flag the construct only properties */ + if (spec->flags & G_PARAM_CONSTRUCT_ONLY) + property_def->construct_only = TRUE; + + if (!property_def->id || !property_def->name) + { + g_critical ("No name or id for " + "glade_property_def_new_from_spec, failed."); + goto failed; + } + + property_def->tooltip = g_strdup (g_param_spec_get_blurb (spec)); + property_def->orig_def = glade_property_def_get_default_from_spec (spec); + property_def->def = glade_property_def_get_default_from_spec (spec); + + g_type_class_unref (gtk_widget_class); + return property_def; + +failed: + glade_property_def_free (property_def); + g_type_class_unref (gtk_widget_class); + return NULL; +} + +/** + * glade_property_def_new_from_spec: + * @adaptor: A generic pointer (i.e. a #GladeWidgetAdaptor) + * @spec: A #GParamSpec + * + * Returns: a newly created #GladePropertyDef based on @spec + * or %NULL if its unsupported. + */ +GladePropertyDef * +glade_property_def_new_from_spec (GladeWidgetAdaptor *adaptor, GParamSpec *spec) +{ + return glade_property_def_new_from_spec_full (adaptor, spec, TRUE); +} + +/** + * glade_property_def_is_visible: + * @property_def: A #GladePropertyDef + * + * + * Returns: whether or not to show this property in the editor + */ +gboolean +glade_property_def_is_visible (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->visible; +} + +/** + * glade_property_def_set_adaptor: + * @property_def: A #GladePropertyDef + * @adaptor: (transfer full): A #GladeWidgetAdaptor + */ +void +glade_property_def_set_adaptor (GladePropertyDef *property_def, + GladeWidgetAdaptor *adaptor) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + property_def->adaptor = adaptor; +} + +/** + * glade_property_def_get_adaptor: + * @property_def: A #GladePropertyDef + * + * Returns: (transfer none): The #GladeWidgetAdaptor associated with the @property_def + */ +GladeWidgetAdaptor * +glade_property_def_get_adaptor (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->adaptor; +} + +/** + * glade_property_def_get_pspec: + * @property_def: A #GladePropertyDef + * + * Returns: (transfer none): The #GParamSpec associated with the @property_def + */ +GParamSpec * +glade_property_def_get_pspec (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->pspec; +} + +/** + * glade_property_def_set_pspec: + * @property_def: A #GladePropertyDef + * @pspec: (transfer full): A #GParamSpec + */ +void +glade_property_def_set_pspec (GladePropertyDef *property_def, + GParamSpec *pspec) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + property_def->pspec = pspec; +} + +void +glade_property_def_set_is_packing (GladePropertyDef *property_def, + gboolean is_packing) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + property_def->packing = is_packing; +} + +gboolean +glade_property_def_get_is_packing (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->packing; +} + +gboolean +glade_property_def_save (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->save; +} + +gboolean +glade_property_def_save_always (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->save_always; +} + +void +glade_property_def_set_virtual (GladePropertyDef *property_def, + gboolean value) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + property_def->virt = value; +} + +gboolean +glade_property_def_get_virtual (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->virt; +} + +void +glade_property_def_set_ignore (GladePropertyDef *property_def, + gboolean ignore) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + property_def->ignore = ignore; +} + +gboolean +glade_property_def_get_ignore (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->ignore; +} + +/** + * glade_property_def_is_object: + * @property_def: A #GladePropertyDef + * + * Returns: whether or not this is an object property + * that refers to another object in this project. + */ +gboolean +glade_property_def_is_object (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return (GLADE_IS_PARAM_SPEC_OBJECTS (property_def->pspec) || + (G_IS_PARAM_SPEC_OBJECT (property_def->pspec) && + property_def->pspec->value_type != GDK_TYPE_PIXBUF && + property_def->pspec->value_type != G_TYPE_FILE)); +} + +void +glade_property_def_set_name (GladePropertyDef *property_def, + const gchar *name) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + g_free (property_def->name); + property_def->name = g_strdup (name); +} + +const gchar * +glade_property_def_get_name (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->name; +} + +void +glade_property_def_set_tooltip (GladePropertyDef *property_def, + const gchar *tooltip) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + g_free (property_def->tooltip); + property_def->tooltip = g_strdup (tooltip); +} + +const gchar * +glade_property_def_get_tooltip (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->tooltip; +} + +void +glade_property_def_set_construct_only (GladePropertyDef *property_def, + gboolean construct_only) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + property_def->construct_only = construct_only; +} + +gboolean +glade_property_def_get_construct_only (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->construct_only; +} + +const GValue * +glade_property_def_get_default (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->def; +} + +const GValue * +glade_property_def_get_original_default (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->orig_def; +} + +gboolean +glade_property_def_translatable (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->translatable; +} + +gboolean +glade_property_def_needs_sync (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->needs_sync; +} + +gboolean +glade_property_def_query (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->query; +} + +gboolean +glade_property_def_atk (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->atk; +} + +gboolean +glade_property_def_common (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->common; +} + +gboolean +glade_property_def_parentless_widget (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->parentless_widget; +} + +gboolean +glade_property_def_optional (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->optional; +} + +gboolean +glade_property_def_optional_default (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->optional_default; +} + +gboolean +glade_property_def_multiline (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->multiline; +} + +gboolean +glade_property_def_stock (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->stock; +} + +gboolean +glade_property_def_stock_icon (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->stock_icon; +} + +gboolean +glade_property_def_transfer_on_paste (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->transfer_on_paste; +} + +gboolean +glade_property_def_custom_layout (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->custom_layout; +} + +gdouble +glade_property_def_weight (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), -1.0); + + return property_def->weight; +} + +const gchar * +glade_property_def_create_type (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->create_type; +} + +guint16 +glade_property_def_since_major (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), 0); + + return property_def->version_since_major; +} + +guint16 +glade_property_def_since_minor (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), 0); + + return property_def->version_since_minor; +} + +guint16 +glade_property_def_deprecated_since_major (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), 0); + + return property_def->deprecated_since_major; +} + +guint16 +glade_property_def_deprecated_since_minor (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), 0); + + return property_def->deprecated_since_minor; +} + +void +_glade_property_def_reset_version (GladePropertyDef *property_def) +{ + g_return_if_fail (GLADE_IS_PROPERTY_DEF (property_def)); + + property_def->version_since_major = 0; + property_def->version_since_minor = 0; + property_def->deprecated_since_major = 0; + property_def->deprecated_since_minor = 0; +} + +gboolean +glade_property_def_deprecated (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->deprecated; +} + +const gchar * +glade_property_def_id (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), NULL); + + return property_def->id; +} + +gboolean +glade_property_def_themed_icon (GladePropertyDef *property_def) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + return property_def->themed_icon; +} + +/** + * gpc_read_displayable_values_from_node: + * @node: a GLADE_TAG_DISPLAYABLE_VALUES node + * @values: an array of the values wich node overrides. + * @n_values: the size of @values + * + * Reads and caches displayable values from the catalog + */ +static void +gpc_read_displayable_values_from_node (GladeXmlNode *node, + GladePropertyDef *property_def, + const gchar *domain) +{ + gpointer the_class = g_type_class_ref (property_def->pspec->value_type); + GladeXmlNode *child; + GEnumValue *enum_values = NULL; + GFlagsValue *flags_values = NULL; + gint n_values, registered_values = 0; + + if (G_IS_PARAM_SPEC_ENUM (property_def->pspec)) + { + GEnumClass *eclass = the_class; + enum_values = eclass->values; + n_values = eclass->n_values; + } + else + { + GFlagsClass *fclass = the_class; + flags_values = fclass->values; + n_values = fclass->n_values; + } + + if ((child = glade_xml_search_child (node, GLADE_TAG_VALUE)) == NULL) + return; + + for (child = glade_xml_node_get_children (node); child; child = glade_xml_node_next (child)) + { + gint i; + gchar *id, *name; + GEnumValue *enum_val; + GFlagsValue *flags_val; + gboolean disabled; + + id = glade_xml_get_property_string_required (child, GLADE_TAG_ID, NULL); + if (!id) continue; + + disabled = glade_xml_get_property_boolean (child, GLADE_TAG_DISABLED, FALSE); + + if (!disabled) + { + name = glade_xml_get_property_string_required (child, GLADE_TAG_NAME, NULL); + if (!name) continue; + } + else + name = NULL; + + for (i = 0; i < n_values; i++) + { + /* is it a match ?? */ + if ((G_IS_PARAM_SPEC_ENUM (property_def->pspec) && + (strcmp (id, enum_values[i].value_name) == 0 || + strcmp (id, enum_values[i].value_nick) == 0)) || + (G_IS_PARAM_SPEC_FLAGS (property_def->pspec) && + (strcmp (id, flags_values[i].value_name) == 0 || + strcmp (id, flags_values[i].value_nick) == 0))) + { + registered_values++; + + if (G_IS_PARAM_SPEC_ENUM (property_def->pspec)) + { + enum_val = &enum_values[i]; + glade_register_displayable_value (property_def->pspec->value_type, + enum_val->value_nick, + domain, name); + if (disabled) + glade_displayable_value_set_disabled (property_def->pspec->value_type, + enum_val->value_nick, + TRUE); + } + else + { + flags_val = &flags_values[i]; + glade_register_displayable_value (property_def->pspec->value_type, + flags_val->value_nick, + domain, name); + if (disabled) + glade_displayable_value_set_disabled (property_def->pspec->value_type, + flags_val->value_nick, + TRUE); + } + break; + } + } + + g_free (id); + g_free (name); + } + + if (n_values != registered_values) + g_message ("%d missing displayable value for %s::%s", + n_values - registered_values, + glade_widget_adaptor_get_name (property_def->adaptor), property_def->id); + + g_type_class_unref (the_class); + +} + +/** + * glade_property_def_make_adjustment: + * @property_def: a pointer to the property class + * + * Creates and appropriate GtkAdjustment for use in the editor + * + * Returns: (transfer full): An appropriate #GtkAdjustment for use in the Property editor + */ +GtkAdjustment * +glade_property_def_make_adjustment (GladePropertyDef *property_def) +{ + GtkAdjustment *adjustment; + gdouble min = 0, max = 0, def = 0; + gboolean float_range = FALSE; + + g_return_val_if_fail (property_def != NULL, NULL); + g_return_val_if_fail (property_def->pspec != NULL, NULL); + + if (G_IS_PARAM_SPEC_INT (property_def->pspec)) + { + min = (gdouble) ((GParamSpecInt *) property_def->pspec)->minimum; + max = (gdouble) ((GParamSpecInt *) property_def->pspec)->maximum; + def = (gdouble) ((GParamSpecInt *) property_def->pspec)->default_value; + } + else if (G_IS_PARAM_SPEC_UINT (property_def->pspec)) + { + min = (gdouble) ((GParamSpecUInt *) property_def->pspec)->minimum; + max = (gdouble) ((GParamSpecUInt *) property_def->pspec)->maximum; + def = (gdouble) ((GParamSpecUInt *) property_def->pspec)->default_value; + } + else if (G_IS_PARAM_SPEC_LONG (property_def->pspec)) + { + min = (gdouble) ((GParamSpecLong *) property_def->pspec)->minimum; + max = (gdouble) ((GParamSpecLong *) property_def->pspec)->maximum; + def = (gdouble) ((GParamSpecLong *) property_def->pspec)->default_value; + } + else if (G_IS_PARAM_SPEC_ULONG (property_def->pspec)) + { + min = (gdouble) ((GParamSpecULong *) property_def->pspec)->minimum; + max = (gdouble) ((GParamSpecULong *) property_def->pspec)->maximum; + def = + (gdouble) ((GParamSpecULong *) property_def->pspec)->default_value; + } + else if (G_IS_PARAM_SPEC_INT64 (property_def->pspec)) + { + min = (gdouble) ((GParamSpecInt64 *) property_def->pspec)->minimum; + max = (gdouble) ((GParamSpecInt64 *) property_def->pspec)->maximum; + def = + (gdouble) ((GParamSpecInt64 *) property_def->pspec)->default_value; + } + else if (G_IS_PARAM_SPEC_UINT64 (property_def->pspec)) + { + min = (gdouble) ((GParamSpecUInt64 *) property_def->pspec)->minimum; + max = (gdouble) ((GParamSpecUInt64 *) property_def->pspec)->maximum; + def = + (gdouble) ((GParamSpecUInt64 *) property_def->pspec)->default_value; + } + else if (G_IS_PARAM_SPEC_FLOAT (property_def->pspec)) + { + float_range = TRUE; + min = ((GParamSpecFloat *) property_def->pspec)->minimum; + max = ((GParamSpecFloat *) property_def->pspec)->maximum; + def = ((GParamSpecFloat *) property_def->pspec)->default_value; + } + else if (G_IS_PARAM_SPEC_DOUBLE (property_def->pspec)) + { + float_range = TRUE; + min = (gdouble) ((GParamSpecDouble *) property_def->pspec)->minimum; + max = (gdouble) ((GParamSpecDouble *) property_def->pspec)->maximum; + def = + (gdouble) ((GParamSpecDouble *) property_def->pspec)->default_value; + } + else + { + g_critical ("Can't make adjustment for pspec type %s", + g_type_name (G_PARAM_SPEC_TYPE (property_def->pspec))); + } + + adjustment = (GtkAdjustment *) gtk_adjustment_new (def, min, max, + float_range ? + FLOATING_STEP_INCREMENT : + NUMERICAL_STEP_INCREMENT, + float_range ? + FLOATING_PAGE_INCREMENT : + NUMERICAL_PAGE_INCREMENT, + float_range ? + FLOATING_PAGE_SIZE : + NUMERICAL_PAGE_SIZE); + return adjustment; +} + + +static GParamSpec * +glade_property_def_parse_specifications (GladePropertyDef *property_def, + GladeXmlNode *spec_node) +{ + gchar *string; + GType spec_type = 0, value_type = 0; + GParamSpec *pspec = NULL; + + if ((string = glade_xml_get_value_string_required + (spec_node, GLADE_TAG_TYPE, + "Need a type of GParamSpec to define")) != NULL) + spec_type = glade_util_get_type_from_name (string, FALSE); + + g_free (string); + + g_return_val_if_fail (spec_type != 0, NULL); + + if (spec_type == G_TYPE_PARAM_ENUM || + spec_type == G_TYPE_PARAM_FLAGS || + spec_type == G_TYPE_PARAM_BOXED || + spec_type == G_TYPE_PARAM_OBJECT || spec_type == GLADE_TYPE_PARAM_OBJECTS) + { + if ((string = glade_xml_get_value_string_required + (spec_node, GLADE_TAG_VALUE_TYPE, + "Need a value type to define enums flags boxed and object specs")) + != NULL) + value_type = glade_util_get_type_from_name (string, FALSE); + + g_free (string); + + g_return_val_if_fail (value_type != 0, NULL); + + if (spec_type == G_TYPE_PARAM_ENUM) + { + GEnumClass *eclass = g_type_class_ref (value_type); + pspec = g_param_spec_enum ("dummy", "dummy", "dummy", + value_type, eclass->minimum, + G_PARAM_READABLE | G_PARAM_WRITABLE); + g_type_class_unref (eclass); + } + else if (spec_type == G_TYPE_PARAM_FLAGS) + pspec = g_param_spec_flags ("dummy", "dummy", "dummy", + value_type, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE); + else if (spec_type == G_TYPE_PARAM_OBJECT) + pspec = g_param_spec_object ("dummy", "dummy", "dummy", + value_type, + G_PARAM_READABLE | G_PARAM_WRITABLE); + else if (spec_type == GLADE_TYPE_PARAM_OBJECTS) + pspec = glade_param_spec_objects ("dummy", "dummy", "dummy", + value_type, + G_PARAM_READABLE | G_PARAM_WRITABLE); + else /* if (spec_type == G_TYPE_PARAM_BOXED) */ + pspec = g_param_spec_boxed ("dummy", "dummy", "dummy", + value_type, + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_STRING) + pspec = g_param_spec_string ("dummy", "dummy", "dummy", + NULL, G_PARAM_READABLE | G_PARAM_WRITABLE); + else if (spec_type == G_TYPE_PARAM_BOOLEAN) + pspec = g_param_spec_boolean ("dummy", "dummy", "dummy", + FALSE, G_PARAM_READABLE | G_PARAM_WRITABLE); + else + { + gchar *minstr, *maxstr; + + minstr = glade_xml_get_value_string (spec_node, GLADE_TAG_MIN_VALUE); + maxstr = glade_xml_get_value_string (spec_node, GLADE_TAG_MAX_VALUE); + + if (spec_type == G_TYPE_PARAM_CHAR) + { + gint8 min = minstr ? minstr[0] : G_MININT8; + gint8 max = maxstr ? maxstr[0] : G_MAXINT8; + + pspec = g_param_spec_char ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_UCHAR) + { + guint8 min = minstr ? minstr[0] : 0; + guint8 max = maxstr ? maxstr[0] : G_MAXUINT8; + + pspec = g_param_spec_uchar ("dummy", "dummy", "dummy", + min, max, 0, + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_INT) + { + gint min = minstr ? g_ascii_strtoll (minstr, NULL, 10) : G_MININT; + gint max = maxstr ? g_ascii_strtoll (maxstr, NULL, 10) : G_MAXINT; + + pspec = g_param_spec_int ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_UINT) + { + guint min = minstr ? g_ascii_strtoll (minstr, NULL, 10) : 0; + guint max = maxstr ? g_ascii_strtoll (maxstr, NULL, 10) : G_MAXUINT; + + pspec = g_param_spec_uint ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_LONG) + { + glong min = minstr ? g_ascii_strtoll (minstr, NULL, 10) : G_MINLONG; + glong max = maxstr ? g_ascii_strtoll (maxstr, NULL, 10) : G_MAXLONG; + + pspec = g_param_spec_long ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_ULONG) + { + gulong min = minstr ? g_ascii_strtoll (minstr, NULL, 10) : 0; + gulong max = maxstr ? g_ascii_strtoll (maxstr, NULL, 10) : G_MAXULONG; + + pspec = g_param_spec_ulong ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_INT64) + { + gint64 min = minstr ? g_ascii_strtoll (minstr, NULL, 10) : G_MININT64; + gint64 max = maxstr ? g_ascii_strtoll (maxstr, NULL, 10) : G_MAXINT64; + + pspec = g_param_spec_int64 ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_UINT64) + { + guint64 min = minstr ? g_ascii_strtoll (minstr, NULL, 10) : 0; + guint64 max = + maxstr ? g_ascii_strtoll (maxstr, NULL, 10) : G_MAXUINT64; + + pspec = g_param_spec_uint64 ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_FLOAT) + { + gfloat min = + minstr ? (float) g_ascii_strtod (minstr, NULL) : G_MINFLOAT; + gfloat max = + maxstr ? (float) g_ascii_strtod (maxstr, NULL) : G_MAXFLOAT; + + pspec = g_param_spec_float ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else if (spec_type == G_TYPE_PARAM_DOUBLE) + { + gdouble min = minstr ? g_ascii_strtod (minstr, NULL) : G_MINFLOAT; + gdouble max = maxstr ? g_ascii_strtod (maxstr, NULL) : G_MAXFLOAT; + + pspec = g_param_spec_float ("dummy", "dummy", "dummy", + min, max, CLAMP (0, min, max), + G_PARAM_READABLE | G_PARAM_WRITABLE); + } + else + g_critical ("Unsupported pspec type %s (value -> string)", + g_type_name (spec_type)); + + g_free (minstr); + g_free (maxstr); + } + return pspec; +} + + +/** + * glade_property_def_update_from_node: + * @node: the property node + * @object_type: the #GType of the owning object + * @property_def_ref: (inout) (nullable): a pointer to the property class + * @domain: the domain to translate catalog strings from + * + * Updates the @property_def_ref with the contents of the node in the xml + * file. Only the values found in the xml file are overridden. + * + * Returns: %TRUE on success. @property_def_ref is set to NULL if the property + * has Disabled="TRUE". + */ +gboolean +glade_property_def_update_from_node (GladeXmlNode *node, + GType object_type, + GladePropertyDef **property_def_ref, + const gchar *domain) +{ + GladePropertyDef *property_def; + GParamSpec *pspec = NULL; + gchar *buf, *translated; + GladeXmlNode *child, *spec_node; + + g_return_val_if_fail (property_def_ref != NULL, FALSE); + + /* for code cleanliness... */ + property_def = *property_def_ref; + + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + g_return_val_if_fail (glade_xml_node_verify (node, GLADE_TAG_PROPERTY), + FALSE); + + /* check the id */ + buf = glade_xml_get_property_string_required (node, GLADE_TAG_ID, NULL); + if (!buf) + return FALSE; + g_free (buf); + + if (glade_xml_get_property_boolean (node, GLADE_TAG_DISABLED, FALSE)) + { + /* Its easier for us to keep disabled properties around and + * only virtually disable them */ + property_def->query = FALSE; + property_def->ignore = TRUE; + property_def->save = FALSE; + property_def->visible = FALSE; + } + + if ((spec_node = + glade_xml_search_child (node, GLADE_TAG_SPECIFICATIONS)) != NULL) + pspec = glade_property_def_parse_specifications (property_def, spec_node); + else if ((buf = glade_xml_get_value_string (node, GLADE_TAG_SPEC)) != NULL) + { + pspec = glade_utils_get_pspec_from_funcname (buf); + g_free (buf); + } + + /* ... get the tooltip from the pspec ... */ + if (pspec != NULL) + { + property_def->pspec = pspec; + + /* Make sure we can tell properties apart by there + * owning class. + */ + property_def->pspec->owner_type = object_type; + + /* We overrode the pspec, now it *is* a virtual property. */ + property_def->virt = TRUE; + + if (strcmp (g_param_spec_get_blurb (property_def->pspec), "dummy") != 0) + { + g_free (property_def->tooltip); + property_def->tooltip = g_strdup (g_param_spec_get_blurb (property_def->pspec)); + } + + if (property_def->name == NULL || + strcmp (g_param_spec_get_nick (property_def->pspec), "dummy") != 0) + { + g_free (property_def->name); + property_def->name = g_strdup (g_param_spec_get_nick (property_def->pspec)); + } + + if (property_def->pspec->flags & G_PARAM_CONSTRUCT_ONLY) + property_def->construct_only = TRUE; + + if (property_def->orig_def) + { + g_value_unset (property_def->orig_def); + g_free (property_def->orig_def); + } + property_def->orig_def = + glade_property_def_get_default_from_spec (property_def->pspec); + + if (property_def->def) + { + g_value_unset (property_def->def); + g_free (property_def->def); + } + property_def->def = glade_property_def_get_default_from_spec (property_def->pspec); + + } + else if (!property_def->pspec) + { + /* If catalog file didn't specify a pspec function + * and this property isn't found by introspection + * we simply delete it from the list always. + */ + glade_property_def_free (property_def); + *property_def_ref = NULL; + return TRUE; + } + + /* Get the default */ + if ((buf = glade_xml_get_property_string (node, GLADE_TAG_DEFAULT)) != NULL) + { + if (property_def->def) + { + g_value_unset (property_def->def); + g_free (property_def->def); + } + property_def->def = + glade_property_def_make_gvalue_from_string (property_def, buf, NULL); + + if (property_def->virt) + { + g_value_unset (property_def->orig_def); + g_free (property_def->orig_def); + property_def->orig_def = + glade_property_def_make_gvalue_from_string (property_def, buf, NULL); + } + + g_free (buf); + } + + /* If needed, update the name... */ + if ((buf = glade_xml_get_property_string (node, GLADE_TAG_NAME)) != NULL) + { + g_free (property_def->name); + + translated = dgettext (domain, buf); + if (buf != translated) + { + /* translated is owned by gettext */ + property_def->name = g_strdup (translated); + g_free (buf); + } + else + { + property_def->name = buf; + } + } + + /* ...and the tooltip */ + if ((buf = glade_xml_get_value_string (node, GLADE_TAG_TOOLTIP)) != NULL) + { + g_free (property_def->tooltip); + + translated = dgettext (domain, buf); + if (buf != translated) + { + /* translated is owned by gettext */ + property_def->tooltip = g_strdup (translated); + g_free (buf); + } + else + { + property_def->tooltip = buf; + } + } + + property_def->multiline = + glade_xml_get_property_boolean (node, GLADE_TAG_MULTILINE, + property_def->multiline); + property_def->construct_only = + glade_xml_get_property_boolean (node, GLADE_TAG_CONSTRUCT_ONLY, + property_def->construct_only); + property_def->translatable = + glade_xml_get_property_boolean (node, GLADE_TAG_TRANSLATABLE, + property_def->translatable); + property_def->common = + glade_xml_get_property_boolean (node, GLADE_TAG_COMMON, property_def->common); + property_def->optional = + glade_xml_get_property_boolean (node, GLADE_TAG_OPTIONAL, + property_def->optional); + property_def->query = + glade_xml_get_property_boolean (node, GLADE_TAG_QUERY, property_def->query); + property_def->save = + glade_xml_get_property_boolean (node, GLADE_TAG_SAVE, property_def->save); + property_def->visible = + glade_xml_get_property_boolean (node, GLADE_TAG_VISIBLE, property_def->visible); + property_def->custom_layout = + glade_xml_get_property_boolean (node, GLADE_TAG_CUSTOM_LAYOUT, + property_def->custom_layout); + property_def->ignore = + glade_xml_get_property_boolean (node, GLADE_TAG_IGNORE, property_def->ignore); + property_def->needs_sync = + glade_xml_get_property_boolean (node, GLADE_TAG_NEEDS_SYNC, + property_def->needs_sync); + property_def->themed_icon = + glade_xml_get_property_boolean (node, GLADE_TAG_THEMED_ICON, + property_def->themed_icon); + property_def->stock = + glade_xml_get_property_boolean (node, GLADE_TAG_STOCK, property_def->stock); + property_def->stock_icon = + glade_xml_get_property_boolean (node, GLADE_TAG_STOCK_ICON, + property_def->stock_icon); + property_def->weight = + glade_xml_get_property_double (node, GLADE_TAG_WEIGHT, property_def->weight); + property_def->transfer_on_paste = + glade_xml_get_property_boolean (node, GLADE_TAG_TRANSFER_ON_PASTE, + property_def->transfer_on_paste); + property_def->save_always = + glade_xml_get_property_boolean (node, GLADE_TAG_SAVE_ALWAYS, + property_def->save_always); + property_def->parentless_widget = + glade_xml_get_property_boolean (node, GLADE_TAG_PARENTLESS_WIDGET, + property_def->parentless_widget); + + + glade_xml_get_property_version (node, GLADE_TAG_VERSION_SINCE, + &property_def->version_since_major, + &property_def->version_since_minor); + + glade_xml_get_property_version (node, GLADE_TAG_DEPRECATED_SINCE, + &property_def->deprecated_since_major, + &property_def->deprecated_since_minor); + + property_def->deprecated = + glade_xml_get_property_boolean (node, + GLADE_TAG_DEPRECATED, + property_def->deprecated); + + + if ((buf = glade_xml_get_property_string + (node, GLADE_TAG_CREATE_TYPE)) != NULL) + { + g_clear_pointer (&property_def->create_type, g_free); + property_def->create_type = buf; + } + + /* If this property's value is an enumeration or flag then we try to get the displayable values */ + if ((G_IS_PARAM_SPEC_ENUM (property_def->pspec) || + G_IS_PARAM_SPEC_FLAGS (property_def->pspec)) && + (child = glade_xml_search_child (node, GLADE_TAG_DISPLAYABLE_VALUES))) + gpc_read_displayable_values_from_node (child, property_def, domain); + + /* Right now allowing the backend to specify that some properties + * go in the atk tab, ideally this shouldnt be needed. + */ + property_def->atk = + glade_xml_get_property_boolean (node, GLADE_TAG_ATK_PROPERTY, property_def->atk); + + if (property_def->optional) + property_def->optional_default = + glade_xml_get_property_boolean (node, GLADE_TAG_OPTIONAL_DEFAULT, + property_def->optional_default); + + /* notify that we changed the property class */ + property_def->is_modified = TRUE; + + return TRUE; +} + + + +/** + * glade_property_def_match: + * @property_def: a #GladePropertyDef + * @comp: a #GladePropertyDef + * + * Returns: whether @property_def and @comp are a match or not + * (properties in separate descendant hierarchies that + * have the same name are not matches). + */ +gboolean +glade_property_def_match (GladePropertyDef *property_def, + GladePropertyDef *comp) +{ + g_return_val_if_fail (property_def != NULL, FALSE); + g_return_val_if_fail (comp != NULL, FALSE); + + return (strcmp (property_def->id, comp->id) == 0 && + property_def->packing == comp->packing && + property_def->pspec->owner_type == comp->pspec->owner_type); +} + + +/** + * glade_property_def_void_value: + * @property_def: a #GladePropertyDef + * @value: a GValue of correct type for @property_def + * + * Returns: Whether @value for this @property_def is voided; a voided value + * can be a %NULL value for boxed or object type param specs. + */ +gboolean +glade_property_def_void_value (GladePropertyDef *property_def, GValue *value) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), FALSE); + + if (G_IS_PARAM_SPEC_OBJECT (property_def->pspec) && + g_value_get_object (value) == NULL) + return TRUE; + else if (G_IS_PARAM_SPEC_BOXED (property_def->pspec) && + g_value_get_boxed (value) == NULL) + return TRUE; + + return FALSE; +} + +/** + * glade_property_def_compare: + * @property_def: a #GladePropertyDef + * @value1: a GValue of correct type for @property_def + * @value2: a GValue of correct type for @property_def + * + * Compares value1 with value2 according to @property_def. + * + * Returns: -1, 0 or +1, if value1 is found to be less than, + * equal to or greater than value2, respectively. + */ +gint +glade_property_def_compare (GladePropertyDef *property_def, + const GValue *value1, + const GValue *value2) +{ + gint retval; + + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (property_def), -1); + + /* GLib does not know how to compare a boxed real value */ + if (G_VALUE_HOLDS_BOXED (value1) || G_VALUE_HOLDS_BOXED (value2)) + { + gchar *val1, *val2; + + /* So boxed types are compared by string and the backend is required to generate + * unique strings for values for this purpose. + * + * NOTE: We could add a pclass option to use the string compare vs. boxed compare... + */ + val1 = + glade_widget_adaptor_string_from_value (property_def->adaptor, property_def, value1); + val2 = + glade_widget_adaptor_string_from_value (property_def->adaptor, property_def, value2); + + if (val1 && val2) + retval = strcmp (val1, val2); + else + retval = val1 - val2; + + g_free (val1); + g_free (val2); + } + else + { + if (G_IS_PARAM_SPEC_STRING (property_def->pspec)) + { + const gchar *value_str1, *value_str2; + + /* in string specs; NULL and '\0' are + * treated as equivalent. + */ + value_str1 = g_value_get_string (value1); + value_str2 = g_value_get_string (value2); + + if (value_str1 == NULL && value_str2 && value_str2[0] == '\0') + return 0; + else if (value_str2 == NULL && value_str1 && value_str1[0] == '\0') + return 0; + } + retval = g_param_values_cmp (property_def->pspec, value1, value2); + } + + return retval; +} + +/** + * glade_property_def_set_weights: + * @properties: (element-type GladePropertyDef): a list of #GladePropertyDef + * @parent: the #GType of the parent + * + * This function assigns "weight" to each property in its natural order staring from 1. + * If parent is 0 weight will be set for every #GladePropertyDef in the list. + * This function will not override weight if it is already set (weight >= 0.0) + */ +void +glade_property_def_set_weights (GList **properties, GType parent) +{ + gint normal = 0, common = 0, packing = 0; + GList *l; + + for (l = *properties; l && l->data; l = g_list_next (l)) + { + GladePropertyDef *property_def = l->data; + + if (property_def->visible && + (parent) ? parent == property_def->pspec->owner_type : TRUE && !property_def->atk) + { + /* Use a different counter for each tab (common, packing and normal) */ + if (property_def->common) + common++; + else if (property_def->packing) + packing++; + else + normal++; + + /* Skip if it is already set */ + if (property_def->weight >= 0.0) + continue; + + /* Special-casing weight of properties for separate tabs */ + if (property_def->common) + property_def->weight = common; + else if (property_def->packing) + property_def->weight = packing; + else + property_def->weight = normal; + } + } +} + +void +glade_property_def_load_defaults_from_spec (GladePropertyDef *property_def) +{ + property_def->orig_def = + glade_property_def_get_default_from_spec (property_def->pspec); + + property_def->def = + glade_property_def_get_default_from_spec (property_def->pspec); +} + diff --git a/gladeui/glade-property-def.h b/gladeui/glade-property-def.h new file mode 100644 index 0000000..4e8e5b5 --- /dev/null +++ b/gladeui/glade-property-def.h @@ -0,0 +1,188 @@ +#ifndef __GLADE_PROPERTY_DEF_H__ +#define __GLADE_PROPERTY_DEF_H__ + +#include +#include +#include + +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PROPERTY_DEF glade_property_def_get_type () + +/* The GladePropertyDef structure parameters of a GladeProperty. + * All entries in the GladeEditor are GladeProperties (except signals) + * All GladeProperties are associated with a GParamSpec. + */ +#define GLADE_PROPERTY_DEF(gpc) ((GladePropertyDef *) gpc) +#define GLADE_IS_PROPERTY_DEF(gpc) (gpc != NULL) + +/** + * GLADE_PROPERTY_DEF_IS_TYPE: + * @gpc: A #GladePropertyDef + * @type: The #GladeEditorPageType to query + * + * Checks if @gpc is good to be loaded as @type + */ +#define GLADE_PROPERTY_DEF_IS_TYPE(gpc, type) \ + (((type) == GLADE_PAGE_GENERAL && \ + !glade_property_def_common (gpc) && \ + !glade_property_def_get_is_packing (gpc) && \ + !glade_property_def_atk (gpc)) || \ + ((type) == GLADE_PAGE_COMMON && glade_property_def_common (gpc)) || \ + ((type) == GLADE_PAGE_PACKING && glade_property_def_get_is_packing (gpc)) || \ + ((type) == GLADE_PAGE_ATK && glade_property_def_atk (gpc)) || \ + ((type) == GLADE_PAGE_QUERY && glade_property_def_query (gpc))) + +/** + * GLADE_PROPERTY_DEF_VERSION_CHECK: + * @def: A #GladePropertyDef + * @major_version: The major version to check + * @minor_version: The minor version to check + * + * Evaluates to %TRUE if @def is available in its owning library version-@major_verion.@minor_version. + * + */ +#define GLADE_PROPERTY_DEF_VERSION_CHECK(def, major_version, minor_version) \ + ((glade_property_def_since_major (GLADE_PROPERTY_DEF (def)) == major_version) ? \ + (glade_property_def_since_minor (GLADE_PROPERTY_DEF (def)) <= minor_version) : \ + (glade_property_def_since_major (GLADE_PROPERTY_DEF (def)) <= major_version)) + +/** + * GLADE_PROPERTY_DEF_DEPRECATED_SINCE_CHECK: + * @def: A #GladePropertyDef + * @major: The major version to check + * @minor: The minor version to check + * + * Evaluates to %TRUE if @def is deprecated in its owning library version-@major.@minor. + * + */ +#define GLADE_PROPERTY_DEF_DEPRECATED_SINCE_CHECK(def, major, minor) \ + ((glade_property_def_deprecated_since_major (def) || glade_property_def_deprecated_since_minor (def)) ? \ + ((glade_property_def_deprecated_since_major (def) == major) ? \ + (glade_property_def_deprecated_since_minor (def) <= minor) : \ + (glade_property_def_deprecated_since_major (def) <= major)) : \ + FALSE) + +#define GLADE_PROPERTY_DEF_OBJECT_DELIMITER ", " + +typedef struct _GladePropertyDef GladePropertyDef; + +GType glade_property_def_get_type (void) G_GNUC_CONST; +GladePropertyDef *glade_property_def_new (GladeWidgetAdaptor *adaptor, + const gchar *id); +GladePropertyDef *glade_property_def_new_from_spec (GladeWidgetAdaptor *adaptor, + GParamSpec *spec); +GladePropertyDef *glade_property_def_new_from_spec_full (GladeWidgetAdaptor *adaptor, + GParamSpec *spec, + gboolean need_handle); +GladePropertyDef *glade_property_def_clone (GladePropertyDef *property_def); +void glade_property_def_free (GladePropertyDef *property_def); + +void glade_property_def_set_adaptor (GladePropertyDef *property_def, + GladeWidgetAdaptor *adaptor); +GladeWidgetAdaptor *glade_property_def_get_adaptor (GladePropertyDef *property_def); +void glade_property_def_set_pspec (GladePropertyDef *property_def, + GParamSpec *pspec); +GParamSpec *glade_property_def_get_pspec (GladePropertyDef *property_def); +void glade_property_def_set_is_packing (GladePropertyDef *property_def, + gboolean is_packing); +gboolean glade_property_def_get_is_packing (GladePropertyDef *property_def); +gboolean glade_property_def_save (GladePropertyDef *property_def); +gboolean glade_property_def_save_always (GladePropertyDef *property_def); +gboolean glade_property_def_is_visible (GladePropertyDef *property_def); +gboolean glade_property_def_is_object (GladePropertyDef *property_def); +void glade_property_def_set_virtual (GladePropertyDef *property_def, + gboolean value); +gboolean glade_property_def_get_virtual (GladePropertyDef *property_def); +void glade_property_def_set_ignore (GladePropertyDef *property_def, + gboolean ignore); +gboolean glade_property_def_get_ignore (GladePropertyDef *property_def); +void glade_property_def_set_name (GladePropertyDef *property_def, + const gchar *name); +const gchar *glade_property_def_get_name (GladePropertyDef *property_def); +void glade_property_def_set_tooltip (GladePropertyDef *property_def, + const gchar *tooltip); +const gchar *glade_property_def_get_tooltip (GladePropertyDef *property_def); +const gchar *glade_property_def_id (GladePropertyDef *property_def); +gboolean glade_property_def_themed_icon (GladePropertyDef *property_def); +void glade_property_def_set_construct_only (GladePropertyDef *property_def, + gboolean construct_only); +gboolean glade_property_def_get_construct_only (GladePropertyDef *property_def); +const GValue *glade_property_def_get_default (GladePropertyDef *property_def); +const GValue *glade_property_def_get_original_default (GladePropertyDef *property_def); +gboolean glade_property_def_translatable (GladePropertyDef *property_def); +gboolean glade_property_def_needs_sync (GladePropertyDef *property_def); + +gboolean glade_property_def_query (GladePropertyDef *property_def); +gboolean glade_property_def_atk (GladePropertyDef *property_def); +gboolean glade_property_def_common (GladePropertyDef *property_def); +gboolean glade_property_def_parentless_widget (GladePropertyDef *property_def); +gboolean glade_property_def_optional (GladePropertyDef *property_def); +gboolean glade_property_def_optional_default (GladePropertyDef *property_def); +gboolean glade_property_def_multiline (GladePropertyDef *property_def); +gboolean glade_property_def_stock (GladePropertyDef *property_def); +gboolean glade_property_def_stock_icon (GladePropertyDef *property_def); +gboolean glade_property_def_transfer_on_paste (GladePropertyDef *property_def); +gboolean glade_property_def_custom_layout (GladePropertyDef *property_def); +gdouble glade_property_def_weight (GladePropertyDef *property_def); + +const gchar *glade_property_def_create_type (GladePropertyDef *property_def); + +guint16 glade_property_def_since_major (GladePropertyDef *property_def); +guint16 glade_property_def_since_minor (GladePropertyDef *property_def); +gboolean glade_property_def_deprecated (GladePropertyDef *property_def); +guint16 glade_property_def_deprecated_since_major (GladePropertyDef *property_def); +guint16 glade_property_def_deprecated_since_minor (GladePropertyDef *property_def); + +GValue *glade_property_def_make_gvalue_from_string (GladePropertyDef *property_def, + const gchar *string, + GladeProject *project); + +gchar *glade_property_def_make_string_from_gvalue (GladePropertyDef *property_def, + const GValue *value); + +GValue *glade_property_def_make_gvalue_from_vl (GladePropertyDef *property_def, + va_list vl); + +void glade_property_def_set_vl_from_gvalue (GladePropertyDef *property_def, + GValue *value, + va_list vl); + +GValue *glade_property_def_make_gvalue (GladePropertyDef *property_def, + ...); + +void glade_property_def_get_from_gvalue (GladePropertyDef *property_def, + GValue *value, + ...); + +gboolean glade_property_def_update_from_node (GladeXmlNode *node, + GType object_type, + GladePropertyDef **property_def_ref, + const gchar *domain); + +GtkAdjustment *glade_property_def_make_adjustment (GladePropertyDef *property_def); + +gboolean glade_property_def_match (GladePropertyDef *property_def, + GladePropertyDef *comp); + +gboolean glade_property_def_void_value (GladePropertyDef *property_def, + GValue *value); + +gint glade_property_def_compare (GladePropertyDef *property_def, + const GValue *value1, + const GValue *value2); + +GValue *glade_property_def_get_default_from_spec (GParamSpec *spec); + +void glade_property_def_set_weights (GList **properties, + GType parent); + +void glade_property_def_load_defaults_from_spec (GladePropertyDef *property_def); + +guint glade_property_def_make_flags_from_string (GType type, + const char *string); +G_END_DECLS + +#endif /* __GLADE_PROPERTY_DEF_H__ */ diff --git a/gladeui/glade-property-label.c b/gladeui/glade-property-label.c new file mode 100644 index 0000000..791836b --- /dev/null +++ b/gladeui/glade-property-label.c @@ -0,0 +1,723 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-popup.h" +#include "glade-editable.h" +#include "glade-property-label.h" + +/* GObjectClass */ +static void glade_property_label_finalize (GObject *object); +static void glade_property_label_dispose (GObject *object); +static void glade_property_label_set_real_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void glade_property_label_get_real_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* GtkWidgetClass */ +static gint glade_property_label_button_press (GtkWidget *widget, + GdkEventButton *event); + +/* GladeEditableInterface */ +static void glade_property_label_editable_init (GladeEditableInterface *iface); + +struct _GladePropertyLabelPrivate +{ + GladeProperty *property; + + GtkWidget *warning; + GtkWidget *label; + GtkWidget *box; + + gulong tooltip_id; /* signal connection id for tooltip changes */ + gulong state_id; /* signal connection id for state changes */ + gulong sensitive_id; /* signal connection id for sensitivity changes */ + gulong enabled_id; /* signal connection id for property enabled changes */ + + gchar *property_name; /* The property name to use when loading by GladeWidget */ + + guint packing : 1; + guint custom_text : 1; + guint custom_tooltip : 1; + guint append_colon : 1; +}; + +enum { + PROP_0, + PROP_PROPERTY, + PROP_PROPERTY_NAME, + PROP_APPEND_COLON, + PROP_PACKING, + PROP_CUSTOM_TEXT, + PROP_CUSTOM_TOOLTIP, +}; + +static GladeEditableInterface *parent_editable_iface; + +G_DEFINE_TYPE_WITH_CODE (GladePropertyLabel, glade_property_label, GTK_TYPE_EVENT_BOX, + G_ADD_PRIVATE (GladePropertyLabel) + G_IMPLEMENT_INTERFACE (GLADE_TYPE_EDITABLE, + glade_property_label_editable_init)) + +static void +glade_property_label_init (GladePropertyLabel *label) +{ + label->priv = glade_property_label_get_instance_private (label); + + label->priv->packing = FALSE; + label->priv->custom_text = FALSE; + label->priv->custom_tooltip = FALSE; + label->priv->append_colon = TRUE; + + gtk_widget_init_template (GTK_WIDGET (label)); +} + +static void +glade_property_label_class_init (GladePropertyLabelClass *class) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (class); + GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (class); + + gobject_class->finalize = glade_property_label_finalize; + gobject_class->dispose = glade_property_label_dispose; + gobject_class->set_property = glade_property_label_set_real_property; + gobject_class->get_property = glade_property_label_get_real_property; + + widget_class->button_press_event = glade_property_label_button_press; + + g_object_class_install_property + (gobject_class, PROP_PROPERTY, + g_param_spec_object ("property", _("Property"), + _("The GladeProperty to display a label for"), + GLADE_TYPE_PROPERTY, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_PROPERTY_NAME, + g_param_spec_string ("property-name", _("Property Name"), + /* To Translators: the property name/id to use to get + * the GladeProperty object from the GladeWidget the + * property belongs to. + */ + _("The property name to use when loading by widget"), + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_APPEND_COLON, + g_param_spec_boolean ("append-colon", _("Append Colon"), + _("Whether to append a colon ':' to the property name"), + TRUE, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_PACKING, + g_param_spec_boolean ("packing", _("Packing"), + /* To Translators: packing properties or child properties are + * properties introduced by GtkContainer and they are not specific + * to the container or child widget but to the relation. + * For more information see GtkContainer docs. + */ + _("Whether the property to load is a packing property or not"), + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_CUSTOM_TEXT, + g_param_spec_string ("custom-text", _("Custom Text"), + _("Custom text to override the property name"), + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_CUSTOM_TOOLTIP, + g_param_spec_string ("custom-tooltip", _("Custom Tooltip"), + _("Custom tooltip to override the property description"), + NULL, G_PARAM_READWRITE)); + + /* Bind to template */ + gtk_widget_class_set_template_from_resource (widget_class, "/org/gnome/gladeui/glade-property-label.ui"); + gtk_widget_class_bind_template_child_private (widget_class, GladePropertyLabel, box); + gtk_widget_class_bind_template_child_private (widget_class, GladePropertyLabel, label); + gtk_widget_class_bind_template_child_private (widget_class, GladePropertyLabel, warning); +} + + +/*********************************************************** + * GObjectClass * + ***********************************************************/ +static void +glade_property_label_finalize (GObject *object) +{ + GladePropertyLabel *label = GLADE_PROPERTY_LABEL (object); + + g_free (label->priv->property_name); + + G_OBJECT_CLASS (glade_property_label_parent_class)->finalize (object); +} + +static void +glade_property_label_dispose (GObject *object) +{ + GladePropertyLabel *label = GLADE_PROPERTY_LABEL (object); + + glade_property_label_set_property (label, NULL); + + G_OBJECT_CLASS (glade_property_label_parent_class)->dispose (object); +} + +static void +glade_property_label_set_real_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladePropertyLabel *label = GLADE_PROPERTY_LABEL (object); + + switch (prop_id) + { + case PROP_PROPERTY: + glade_property_label_set_property (label, g_value_get_object (value)); + break; + case PROP_PROPERTY_NAME: + glade_property_label_set_property_name (label, g_value_get_string (value)); + break; + case PROP_APPEND_COLON: + glade_property_label_set_append_colon (label, g_value_get_boolean (value)); + break; + case PROP_PACKING: + glade_property_label_set_packing (label, g_value_get_boolean (value)); + break; + case PROP_CUSTOM_TEXT: + glade_property_label_set_custom_text (label, g_value_get_string (value)); + break; + case PROP_CUSTOM_TOOLTIP: + glade_property_label_set_custom_tooltip (label, g_value_get_string (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_property_label_get_real_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladePropertyLabel *label = GLADE_PROPERTY_LABEL (object); + + switch (prop_id) + { + case PROP_PROPERTY: + g_value_set_object (value, glade_property_label_get_property (label)); + break; + case PROP_PROPERTY_NAME: + g_value_set_string (value, glade_property_label_get_property_name (label)); + break; + case PROP_PACKING: + g_value_set_boolean (value, glade_property_label_get_packing (label)); + break; + case PROP_APPEND_COLON: + g_value_set_boolean (value, glade_property_label_get_append_colon (label)); + break; + case PROP_CUSTOM_TEXT: + g_value_set_string (value, glade_property_label_get_custom_text (label)); + break; + case PROP_CUSTOM_TOOLTIP: + g_value_set_string (value, glade_property_label_get_custom_tooltip (label)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/******************************************************************************* + * GladeEditableInterface * + *******************************************************************************/ +static void +glade_property_label_load (GladeEditable *editable, + GladeWidget *widget) +{ + GladePropertyLabel *label = GLADE_PROPERTY_LABEL (editable); + GladePropertyLabelPrivate *priv; + GladeProperty *property; + + /* Chain up to default implementation */ + parent_editable_iface->load (editable, widget); + + g_return_if_fail (label->priv->property_name != NULL); + + priv = label->priv; + + if (widget) + { + if (priv->packing) + property = glade_widget_get_pack_property (widget, priv->property_name); + else + property = glade_widget_get_property (widget, priv->property_name); + + glade_property_label_set_property (label, property); + } + else + glade_property_label_set_property (label, NULL); +} + +static void +glade_property_label_set_show_name (GladeEditable *editable, gboolean show_name) +{ +} + +static void +glade_property_label_editable_init (GladeEditableInterface *iface) +{ + parent_editable_iface = g_type_default_interface_peek (GLADE_TYPE_EDITABLE); + + iface->load = glade_property_label_load; + iface->set_show_name = glade_property_label_set_show_name; +} + +/*********************************************************** + * GtkWidgetClass * + ***********************************************************/ +static gint +glade_property_label_button_press (GtkWidget *widget, + GdkEventButton *event) +{ + GladePropertyLabel *label = GLADE_PROPERTY_LABEL (widget); + GladePropertyLabelPrivate *priv = label->priv; + + if (priv->property && glade_popup_is_popup_event (event)) + { + glade_popup_property_pop (priv->property, event); + return TRUE; + } + + return FALSE; +} + +/*********************************************************** + * Callbacks * + ***********************************************************/ +static void +glade_property_label_tooltip_cb (GladeProperty *property, + const gchar *tooltip, + const gchar *insensitive, + const gchar *support, + GladePropertyLabel *label) +{ + GladePropertyLabelPrivate *priv = label->priv; + const gchar *choice_tooltip; + + if (glade_property_get_sensitive (property)) + choice_tooltip = tooltip; + else + choice_tooltip = insensitive; + + if (!priv->custom_tooltip) + gtk_widget_set_tooltip_text (priv->label, choice_tooltip); + + gtk_widget_set_tooltip_text (priv->warning, support); +} + +static void +glade_property_label_sensitivity_cb (GladeProperty *property, + GParamSpec *pspec, + GladePropertyLabel *label) +{ + GladePropertyLabelPrivate *priv = label->priv; + gboolean sensitive; + + sensitive = glade_property_get_enabled (property); + sensitive = sensitive && glade_property_get_sensitive (priv->property); + sensitive = sensitive && (glade_property_get_state (priv->property) & GLADE_STATE_SUPPORT_DISABLED) == 0; + + gtk_widget_set_sensitive (priv->box, sensitive); +} + +static PangoAttrList * +get_modified_attribute (void) +{ + static PangoAttrList *attrs = NULL; + + if (!attrs) + { + PangoAttribute *attr; + + attrs = pango_attr_list_new (); + attr = pango_attr_style_new (PANGO_STYLE_ITALIC); + pango_attr_list_insert (attrs, attr); + } + + return attrs; +} + +static void +glade_property_label_state_cb (GladeProperty *property, + GParamSpec *pspec, + GladePropertyLabel *label) +{ + GladePropertyLabelPrivate *priv = label->priv; + + if (!priv->property) + return; + + /* refresh label */ + if ((glade_property_get_state (priv->property) & GLADE_STATE_CHANGED) != 0) + gtk_label_set_attributes (GTK_LABEL (priv->label), get_modified_attribute()); + else + gtk_label_set_attributes (GTK_LABEL (priv->label), NULL); + + /* refresh icon */ + if ((glade_property_get_state (priv->property) & GLADE_STATE_UNSUPPORTED) != 0) + gtk_widget_show (priv->warning); + else + gtk_widget_hide (priv->warning); +} + +static void +glade_property_label_property_finalized (GladePropertyLabel *label, + GladeProperty *where_property_was) +{ + /* Silent disconnect */ + label->priv->property = NULL; + label->priv->tooltip_id = 0; + label->priv->state_id = 0; + label->priv->sensitive_id = 0; + label->priv->enabled_id = 0; +} + +/*********************************************************** + * API * + ***********************************************************/ +GtkWidget * +glade_property_label_new (void) +{ + return g_object_new (GLADE_TYPE_PROPERTY_LABEL, NULL); +} + +void +glade_property_label_set_property_name (GladePropertyLabel *label, + const gchar *property_name) +{ + GladePropertyLabelPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_LABEL (label)); + + priv = label->priv; + + if (g_strcmp0 (priv->property_name, property_name)) + { + g_free (priv->property_name); + priv->property_name = g_strdup (property_name); + + g_object_notify (G_OBJECT (label), "property-name"); + } +} + +const gchar * +glade_property_label_get_property_name (GladePropertyLabel *label) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_LABEL (label), NULL); + + return label->priv->property_name; +} + +void +glade_property_label_set_append_colon (GladePropertyLabel *label, + gboolean append_colon) +{ + GladePropertyLabelPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_LABEL (label)); + + priv = label->priv; + + if (priv->append_colon != append_colon) + { + priv->append_colon = append_colon; + + g_object_notify (G_OBJECT (label), "append-colon"); + } +} + +gboolean +glade_property_label_get_append_colon (GladePropertyLabel *label) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_LABEL (label), FALSE); + + return label->priv->append_colon; +} + +void +glade_property_label_set_packing (GladePropertyLabel *label, + gboolean packing) +{ + GladePropertyLabelPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_LABEL (label)); + + priv = label->priv; + + if (priv->packing != packing) + { + priv->packing = packing; + + g_object_notify (G_OBJECT (label), "packing"); + } +} + +gboolean +glade_property_label_get_packing (GladePropertyLabel *label) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_LABEL (label), FALSE); + + return label->priv->packing; +} + +void +glade_property_label_set_custom_text (GladePropertyLabel *label, + const gchar *custom_text) +{ + GladePropertyLabelPrivate *priv; + gboolean changed = FALSE; + + g_return_if_fail (GLADE_IS_PROPERTY_LABEL (label)); + + priv = label->priv; + + if (custom_text) + { + if (!priv->custom_text) + changed = TRUE; + + priv->custom_text = TRUE; + + gtk_label_set_markup (GTK_LABEL (priv->label), custom_text); + } + else + { + if (priv->custom_text) + changed = TRUE; + + priv->custom_text = FALSE; + + if (priv->property) + glade_property_label_state_cb (priv->property, NULL, label); + } + + if (changed) + g_object_notify (G_OBJECT (label), "custom-text"); +} + +const gchar * +glade_property_label_get_custom_text (GladePropertyLabel *label) +{ + GladePropertyLabelPrivate *priv; + + g_return_val_if_fail (GLADE_IS_PROPERTY_LABEL (label), NULL); + + priv = label->priv; + + if (priv->custom_text) + return gtk_label_get_text (GTK_LABEL (priv->label)); + + return NULL; +} + +void +glade_property_label_set_custom_tooltip (GladePropertyLabel *label, + const gchar *custom_tooltip) +{ + GladePropertyLabelPrivate *priv; + gboolean changed = FALSE; + + g_return_if_fail (GLADE_IS_PROPERTY_LABEL (label)); + + priv = label->priv; + + if (custom_tooltip) + { + if (!priv->custom_tooltip) + changed = TRUE; + + priv->custom_tooltip = TRUE; + + gtk_widget_set_tooltip_text (GTK_WIDGET (priv->label), custom_tooltip); + } + else + { + if (priv->custom_tooltip) + changed = TRUE; + + priv->custom_tooltip = FALSE; + + if (priv->property) + { + GladePropertyDef *pdef = glade_property_get_def (priv->property); + + glade_property_label_tooltip_cb + (priv->property, glade_property_def_get_tooltip (pdef), + glade_propert_get_insensitive_tooltip (priv->property), + glade_property_get_support_warning (priv->property), label); + } + } + + if (changed) + g_object_notify (G_OBJECT (label), "custom-tooltip"); +} + +const gchar * +glade_property_label_get_custom_tooltip (GladePropertyLabel *label) +{ + GladePropertyLabelPrivate *priv; + + g_return_val_if_fail (GLADE_IS_PROPERTY_LABEL (label), NULL); + + priv = label->priv; + + if (priv->custom_tooltip) + return gtk_widget_get_tooltip_text (priv->label); + + return NULL; +} + +void +glade_property_label_set_property (GladePropertyLabel *label, + GladeProperty *property) +{ + GladePropertyLabelPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_LABEL (label)); + g_return_if_fail (property == NULL || GLADE_IS_PROPERTY (property)); + + priv = label->priv; + + if (priv->property != property) + { + + /* Disconnect last */ + if (priv->property) + { + if (priv->tooltip_id > 0) + g_signal_handler_disconnect (priv->property, priv->tooltip_id); + if (priv->state_id > 0) + g_signal_handler_disconnect (priv->property, priv->state_id); + if (priv->sensitive_id > 0) + g_signal_handler_disconnect (priv->property, priv->sensitive_id); + if (priv->enabled_id > 0) + g_signal_handler_disconnect (priv->property, priv->enabled_id); + + priv->tooltip_id = 0; + priv->state_id = 0; + priv->sensitive_id = 0; + priv->enabled_id = 0; + + g_object_weak_unref (G_OBJECT (priv->property), + (GWeakNotify) glade_property_label_property_finalized, label); + } + + priv->property = property; + + /* Connect new */ + if (priv->property) + { + GladePropertyDef *pdef = glade_property_get_def (priv->property); + + priv->tooltip_id = + g_signal_connect (G_OBJECT (priv->property), + "tooltip-changed", + G_CALLBACK (glade_property_label_tooltip_cb), + label); + priv->sensitive_id = + g_signal_connect (G_OBJECT (priv->property), + "notify::sensitive", + G_CALLBACK (glade_property_label_sensitivity_cb), + label); + priv->state_id = + g_signal_connect (G_OBJECT (priv->property), + "notify::state", + G_CALLBACK (glade_property_label_state_cb), label); + priv->enabled_id = + g_signal_connect (G_OBJECT (priv->property), + "notify::enabled", + G_CALLBACK (glade_property_label_sensitivity_cb), + label); + + g_object_weak_ref (G_OBJECT (priv->property), + (GWeakNotify) glade_property_label_property_finalized, label); + + /* Load initial tooltips + */ + glade_property_label_tooltip_cb + (property, glade_property_def_get_tooltip (pdef), + glade_propert_get_insensitive_tooltip (property), + glade_property_get_support_warning (property), label); + + /* Load initial sensitive state. + */ + glade_property_label_sensitivity_cb (property, NULL, label); + + /* Load initial label state + */ + glade_property_label_state_cb (property, NULL, label); + + if (!priv->custom_text) + { + if (priv->append_colon) + { + gchar *text = g_strdup_printf ("%s:", glade_property_def_get_name (pdef)); + gtk_label_set_text (GTK_LABEL (priv->label), text); + g_free (text); + } + else + { + gtk_label_set_text (GTK_LABEL (priv->label), + glade_property_def_get_name (pdef)); + } + } + } + + g_object_notify (G_OBJECT (label), "property"); + } +} + +/** + * glade_property_label_get_property: + * @label: a #GladePropertyLabel + * + * Returns: (transfer none): A #GladeProperty + */ +GladeProperty * +glade_property_label_get_property (GladePropertyLabel *label) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_LABEL (label), NULL); + + return label->priv->property; +} diff --git a/gladeui/glade-property-label.h b/gladeui/glade-property-label.h new file mode 100644 index 0000000..2bef9eb --- /dev/null +++ b/gladeui/glade-property-label.h @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ +#ifndef __GLADE_PROPERTY_LABEL_H__ +#define __GLADE_PROPERTY_LABEL_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PROPERTY_LABEL (glade_property_label_get_type ()) +#define GLADE_PROPERTY_LABEL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PROPERTY_LABEL, GladePropertyLabel)) +#define GLADE_PROPERTY_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PROPERTY_LABEL, GladePropertyLabelClass)) +#define GLADE_IS_PROPERTY_LABEL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PROPERTY_LABEL)) +#define GLADE_IS_PROPERTY_LABEL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PROPERTY_LABEL)) +#define GLADE_PROPERTY_LABEL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PROPERTY_LABEL, GladePropertyLabelClass)) + +typedef struct _GladePropertyLabel GladePropertyLabel; +typedef struct _GladePropertyLabelClass GladePropertyLabelClass; +typedef struct _GladePropertyLabelPrivate GladePropertyLabelPrivate; + +struct _GladePropertyLabel +{ + /*< private >*/ + GtkEventBox box; + + GladePropertyLabelPrivate *priv; +}; + +struct _GladePropertyLabelClass +{ + GtkEventBoxClass parent_class; +}; + +GType glade_property_label_get_type (void) G_GNUC_CONST; + +GtkWidget *glade_property_label_new (void); + +void glade_property_label_set_property_name (GladePropertyLabel *label, + const gchar *property_name); +const gchar *glade_property_label_get_property_name (GladePropertyLabel *label); +void glade_property_label_set_append_colon (GladePropertyLabel *label, + gboolean append_colon); +gboolean glade_property_label_get_append_colon (GladePropertyLabel *label); +void glade_property_label_set_packing (GladePropertyLabel *label, + gboolean packing); +gboolean glade_property_label_get_packing (GladePropertyLabel *label); + +void glade_property_label_set_custom_text (GladePropertyLabel *label, + const gchar *custom_text); +const gchar *glade_property_label_get_custom_text (GladePropertyLabel *label); +void glade_property_label_set_custom_tooltip(GladePropertyLabel *label, + const gchar *custom_tooltip); +const gchar *glade_property_label_get_custom_tooltip(GladePropertyLabel *label); + +void glade_property_label_set_property (GladePropertyLabel *label, + GladeProperty *property); +GladeProperty *glade_property_label_get_property (GladePropertyLabel *label); + +G_END_DECLS + +#endif /* __GLADE_PROPERTY_LABEL_H__ */ diff --git a/gladeui/glade-property-label.ui b/gladeui/glade-property-label.ui new file mode 100644 index 0000000..4908450 --- /dev/null +++ b/gladeui/glade-property-label.ui @@ -0,0 +1,64 @@ + + + + + + diff --git a/gladeui/glade-property-shell.c b/gladeui/glade-property-shell.c new file mode 100644 index 0000000..a7fec34 --- /dev/null +++ b/gladeui/glade-property-shell.c @@ -0,0 +1,568 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-popup.h" +#include "glade-editable.h" +#include "glade-property-shell.h" +#include "glade-marshallers.h" + +/* GObjectClass */ +static void glade_property_shell_finalize (GObject *object); +static void glade_property_shell_set_real_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec); +static void glade_property_shell_get_real_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec); + +/* GladeEditableInterface */ +static void glade_property_shell_editable_init (GladeEditableInterface *iface); + +struct _GladePropertyShellPrivate +{ + /* Current State */ + GladeWidgetAdaptor *adaptor; + GladeEditorProperty *property_editor; + gulong pre_commit_id; + gulong post_commit_id; + + /* Properties, used to load the internal editor */ + GType editor_type; + gchar *property_name; + gchar *custom_text; + guint packing : 1; + guint use_command : 1; + guint disable_check : 1; +}; + +enum { + PROP_0, + PROP_PROPERTY_NAME, + PROP_PACKING, + PROP_USE_COMMAND, + PROP_EDITOR_TYPE, + PROP_CUSTOM_TEXT, + PROP_DISABLE_CHECK +}; + +enum +{ + PRE_COMMIT, + POST_COMMIT, + LAST_SIGNAL +}; + +static guint glade_property_shell_signals[LAST_SIGNAL] = { 0, }; + +static GladeEditableInterface *parent_editable_iface; + +G_DEFINE_TYPE_WITH_CODE (GladePropertyShell, glade_property_shell, GTK_TYPE_BOX, + G_ADD_PRIVATE (GladePropertyShell) + G_IMPLEMENT_INTERFACE (GLADE_TYPE_EDITABLE, + glade_property_shell_editable_init)); + +static void +glade_property_shell_init (GladePropertyShell *shell) +{ + shell->priv = glade_property_shell_get_instance_private (shell); + + shell->priv->packing = FALSE; + shell->priv->use_command = TRUE; + shell->priv->disable_check = FALSE; +} + +static void +glade_property_shell_class_init (GladePropertyShellClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->finalize = glade_property_shell_finalize; + gobject_class->set_property = glade_property_shell_set_real_property; + gobject_class->get_property = glade_property_shell_get_real_property; + + g_object_class_install_property + (gobject_class, PROP_PROPERTY_NAME, + g_param_spec_string ("property-name", _("Property Name"), + _("The property name to use when loading by widget"), + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_PACKING, + g_param_spec_boolean ("packing", _("Packing"), + _("Whether the property to load is a packing property or not"), + FALSE, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_USE_COMMAND, + g_param_spec_boolean ("use-command", _("Use Command"), + _("Whether to use the GladeCommand API when modifying properties"), + TRUE, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_EDITOR_TYPE, + g_param_spec_string ("editor-type", _("Editor Property Type Name"), + _("Specify the actual editor property type name to use for this shell"), + NULL, G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY)); + + g_object_class_install_property + (gobject_class, PROP_CUSTOM_TEXT, + g_param_spec_string ("custom-text", _("Custom Text"), + _("Custom Text to display in the property label"), + NULL, G_PARAM_READWRITE)); + + g_object_class_install_property + (gobject_class, PROP_DISABLE_CHECK, + g_param_spec_boolean ("disable-check", _("Disable Check"), + _("Whether to explicitly disable the check button"), + FALSE, G_PARAM_READWRITE)); + + /** + * GladePropertyShell::pre-commit: + * @gladeeditorproperty: the #GladeEditorProperty which changed value + * @arg1: the new #GValue to commit. + * + * Emitted before a property's value is committed, can be useful to serialize + * commands before a property's commit command from custom editors. + */ + glade_property_shell_signals[PRE_COMMIT] = + g_signal_new ("pre-commit", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _glade_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); + + /** + * GladePropertyShell::post-commit: + * @gladeeditorproperty: the #GladeEditorProperty which changed value + * @arg1: the new #GValue to commit. + * + * Emitted after a property's value is committed, can be useful to serialize + * commands after a property's commit command from custom editors. + */ + glade_property_shell_signals[POST_COMMIT] = + g_signal_new ("post-commit", + G_TYPE_FROM_CLASS (gobject_class), + G_SIGNAL_RUN_LAST, + 0, NULL, NULL, + _glade_marshal_VOID__POINTER, + G_TYPE_NONE, 1, G_TYPE_POINTER); +} + + +/*********************************************************** + * GObjectClass * + ***********************************************************/ +static void +glade_property_shell_finalize (GObject *object) +{ + GladePropertyShell *shell = GLADE_PROPERTY_SHELL (object); + + g_free (shell->priv->property_name); + g_free (shell->priv->custom_text); + + G_OBJECT_CLASS (glade_property_shell_parent_class)->finalize (object); +} + +static void +glade_property_shell_set_real_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladePropertyShell *shell = GLADE_PROPERTY_SHELL (object); + GladePropertyShellPrivate *priv = shell->priv; + const gchar *type_name = NULL; + GType type = 0; + + switch (prop_id) + { + case PROP_PROPERTY_NAME: + glade_property_shell_set_property_name (shell, g_value_get_string (value)); + break; + case PROP_PACKING: + glade_property_shell_set_packing (shell, g_value_get_boolean (value)); + break; + case PROP_USE_COMMAND: + glade_property_shell_set_use_command (shell, g_value_get_boolean (value)); + break; + case PROP_EDITOR_TYPE: + type_name = g_value_get_string (value); + + if (type_name) + type = glade_util_get_type_from_name (type_name, FALSE); + + if (type > 0 && !g_type_is_a (type, GLADE_TYPE_EDITOR_PROPERTY)) + g_warning ("Editor type '%s' is not a GladeEditorProperty", type_name); + else + priv->editor_type = type; + + break; + case PROP_CUSTOM_TEXT: + glade_property_shell_set_custom_text (shell, g_value_get_string (value)); + break; + case PROP_DISABLE_CHECK: + glade_property_shell_set_disable_check (shell, g_value_get_boolean (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_property_shell_get_real_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladePropertyShell *shell = GLADE_PROPERTY_SHELL (object); + + switch (prop_id) + { + case PROP_PROPERTY_NAME: + g_value_set_string (value, glade_property_shell_get_property_name (shell)); + break; + case PROP_PACKING: + g_value_set_boolean (value, glade_property_shell_get_packing (shell)); + break; + case PROP_USE_COMMAND: + g_value_set_boolean (value, glade_property_shell_get_use_command (shell)); + break; + case PROP_CUSTOM_TEXT: + g_value_set_string (value, glade_property_shell_get_custom_text (shell)); + break; + case PROP_DISABLE_CHECK: + g_value_set_boolean (value, glade_property_shell_get_disable_check (shell)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/******************************************************************************* + * GladeEditableInterface * + *******************************************************************************/ +static void +propagate_pre_commit (GladeEditorProperty *property, + GValue *value, + GladePropertyShell *shell) +{ + g_signal_emit (G_OBJECT (shell), glade_property_shell_signals[PRE_COMMIT], 0, value); +} + +static void +propagate_post_commit (GladeEditorProperty *property, + GValue *value, + GladePropertyShell *shell) +{ + g_signal_emit (G_OBJECT (shell), glade_property_shell_signals[POST_COMMIT], 0, value); +} + +static void +glade_property_shell_set_eprop (GladePropertyShell *shell, + GladeEditorProperty *eprop) +{ + GladePropertyShellPrivate *priv = shell->priv; + + if (priv->property_editor != eprop) + { + if (priv->property_editor) + { + g_signal_handler_disconnect (priv->property_editor, priv->pre_commit_id); + g_signal_handler_disconnect (priv->property_editor, priv->post_commit_id); + priv->pre_commit_id = 0; + priv->post_commit_id = 0; + + gtk_widget_destroy (GTK_WIDGET (priv->property_editor)); + } + + priv->property_editor = eprop; + + if (priv->property_editor) + { + glade_editor_property_set_custom_text (priv->property_editor, priv->custom_text); + glade_editor_property_set_disable_check (priv->property_editor, priv->disable_check); + + priv->pre_commit_id = g_signal_connect (priv->property_editor, "commit", + G_CALLBACK (propagate_pre_commit), shell); + priv->post_commit_id = g_signal_connect_after (priv->property_editor, "commit", + G_CALLBACK (propagate_post_commit), shell); + + gtk_container_add (GTK_CONTAINER (shell), GTK_WIDGET (priv->property_editor)); + } + } +} + +static void +glade_property_shell_load (GladeEditable *editable, + GladeWidget *widget) +{ + GladePropertyShell *shell = GLADE_PROPERTY_SHELL (editable); + GladePropertyShellPrivate *priv; + + /* Chain up to default implementation */ + parent_editable_iface->load (editable, widget); + + g_return_if_fail (shell->priv->property_name != NULL); + + priv = shell->priv; + + if (widget) + { + GladeWidgetAdaptor *adaptor = NULL; + + /* Use the parent adaptor if we're a packing property */ + if (priv->packing) + { + GladeWidget *parent = glade_widget_get_parent (widget); + + if (parent) + adaptor = glade_widget_get_adaptor (parent); + } + else + adaptor = glade_widget_get_adaptor (widget); + + /* Need to rebuild the internal editor */ + if (priv->adaptor != adaptor) + { + GladePropertyDef *pdef = NULL; + GladeEditorProperty *eprop = NULL; + + priv->adaptor = adaptor; + + if (adaptor) + { + if (priv->packing) + pdef = glade_widget_adaptor_get_pack_property_def (priv->adaptor, + priv->property_name); + else + pdef = glade_widget_adaptor_get_property_def (priv->adaptor, + priv->property_name); + } + + /* Be forgiving, allow usage of properties that wont work, so that + * some editors can include properties for subclasses, and hide + * those properties if they're not applicable + */ + if (pdef == NULL) + { + priv->property_editor = NULL; + } + /* Construct custom editor property if specified */ + else if (g_type_is_a (priv->editor_type, GLADE_TYPE_EDITOR_PROPERTY)) + { + eprop = g_object_new (priv->editor_type, + "property-def", pdef, + "use-command", priv->use_command, + NULL); + } + else + { + /* Let the adaptor create one */ + eprop = glade_widget_adaptor_create_eprop_by_name (priv->adaptor, + priv->property_name, + priv->packing, + priv->use_command); + } + + glade_property_shell_set_eprop (shell, eprop); + } + + /* If we have an editor for the right adaptor, load it */ + if (priv->property_editor) + glade_editable_load (GLADE_EDITABLE (priv->property_editor), widget); + } + else if (priv->property_editor) + glade_editable_load (GLADE_EDITABLE (priv->property_editor), NULL); +} + +static void +glade_property_shell_set_show_name (GladeEditable *editable, gboolean show_name) +{ +} + +static void +glade_property_shell_editable_init (GladeEditableInterface *iface) +{ + parent_editable_iface = g_type_default_interface_peek (GLADE_TYPE_EDITABLE); + + iface->load = glade_property_shell_load; + iface->set_show_name = glade_property_shell_set_show_name; +} + +/*********************************************************** + * API * + ***********************************************************/ +GtkWidget * +glade_property_shell_new (void) +{ + return g_object_new (GLADE_TYPE_PROPERTY_SHELL, NULL); +} + +void +glade_property_shell_set_property_name (GladePropertyShell *shell, + const gchar *property_name) +{ + GladePropertyShellPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_SHELL (shell)); + + priv = shell->priv; + + if (g_strcmp0 (priv->property_name, property_name) != 0) + { + g_free (priv->property_name); + priv->property_name = g_strdup (property_name); + + g_object_notify (G_OBJECT (shell), "property-name"); + } +} + +const gchar * +glade_property_shell_get_property_name (GladePropertyShell *shell) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_SHELL (shell), NULL); + + return shell->priv->property_name; +} + +void +glade_property_shell_set_custom_text (GladePropertyShell *shell, + const gchar *custom_text) +{ + GladePropertyShellPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_SHELL (shell)); + + priv = shell->priv; + + if (g_strcmp0 (priv->custom_text, custom_text) != 0) + { + g_free (priv->custom_text); + priv->custom_text = g_strdup (custom_text); + + if (priv->property_editor) + glade_editor_property_set_custom_text (priv->property_editor, custom_text); + + g_object_notify (G_OBJECT (shell), "custom-text"); + } +} + +const gchar * +glade_property_shell_get_custom_text (GladePropertyShell *shell) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_SHELL (shell), NULL); + + return shell->priv->custom_text; +} + +void +glade_property_shell_set_packing (GladePropertyShell *shell, + gboolean packing) +{ + GladePropertyShellPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_SHELL (shell)); + + priv = shell->priv; + + if (priv->packing != packing) + { + priv->packing = packing; + + g_object_notify (G_OBJECT (shell), "packing"); + } +} + +gboolean +glade_property_shell_get_packing (GladePropertyShell *shell) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_SHELL (shell), FALSE); + + return shell->priv->packing; +} + +void +glade_property_shell_set_use_command (GladePropertyShell *shell, + gboolean use_command) +{ + GladePropertyShellPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_SHELL (shell)); + + priv = shell->priv; + + if (priv->use_command != use_command) + { + priv->use_command = use_command; + + g_object_notify (G_OBJECT (shell), "use-command"); + } +} + +gboolean +glade_property_shell_get_use_command (GladePropertyShell *shell) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_SHELL (shell), FALSE); + + return shell->priv->use_command; +} + +void +glade_property_shell_set_disable_check (GladePropertyShell *shell, + gboolean disable_check) +{ + GladePropertyShellPrivate *priv; + + g_return_if_fail (GLADE_IS_PROPERTY_SHELL (shell)); + + priv = shell->priv; + + if (priv->disable_check != disable_check) + { + priv->disable_check = disable_check; + + if (priv->property_editor) + g_object_set (priv->property_editor, "disable-check", disable_check, NULL); + + g_object_notify (G_OBJECT (shell), "disable-check"); + } +} + +gboolean +glade_property_shell_get_disable_check (GladePropertyShell *shell) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY_SHELL (shell), FALSE); + + return shell->priv->disable_check; +} diff --git a/gladeui/glade-property-shell.h b/gladeui/glade-property-shell.h new file mode 100644 index 0000000..dfb42c5 --- /dev/null +++ b/gladeui/glade-property-shell.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2013 Tristan Van Berkom. + * + * This library is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Tristan Van Berkom + */ +#ifndef __GLADE_PROPERTY_SHELL_H__ +#define __GLADE_PROPERTY_SHELL_H__ + +#include +#include +#include +#include + +G_BEGIN_DECLS + +#define GLADE_TYPE_PROPERTY_SHELL (glade_property_shell_get_type ()) +#define GLADE_PROPERTY_SHELL(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GLADE_TYPE_PROPERTY_SHELL, GladePropertyShell)) +#define GLADE_PROPERTY_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GLADE_TYPE_PROPERTY_SHELL, GladePropertyShellClass)) +#define GLADE_IS_PROPERTY_SHELL(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GLADE_TYPE_PROPERTY_SHELL)) +#define GLADE_IS_PROPERTY_SHELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GLADE_TYPE_PROPERTY_SHELL)) +#define GLADE_PROPERTY_SHELL_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GLADE_TYPE_PROPERTY_SHELL, GladePropertyShellClass)) + +typedef struct _GladePropertyShell GladePropertyShell; +typedef struct _GladePropertyShellClass GladePropertyShellClass; +typedef struct _GladePropertyShellPrivate GladePropertyShellPrivate; + +struct _GladePropertyShell +{ + /*< private >*/ + GtkBox box; + + GladePropertyShellPrivate *priv; +}; + +struct _GladePropertyShellClass +{ + GtkBoxClass parent_class; +}; + +GType glade_property_shell_get_type (void) G_GNUC_CONST; + +GtkWidget *glade_property_shell_new (void); + +void glade_property_shell_set_property_name (GladePropertyShell *shell, + const gchar *property_name); +const gchar *glade_property_shell_get_property_name (GladePropertyShell *shell); +void glade_property_shell_set_custom_text (GladePropertyShell *shell, + const gchar *custom_text); +const gchar *glade_property_shell_get_custom_text (GladePropertyShell *shell); +void glade_property_shell_set_packing (GladePropertyShell *shell, + gboolean packing); +gboolean glade_property_shell_get_packing (GladePropertyShell *shell); +void glade_property_shell_set_use_command (GladePropertyShell *shell, + gboolean use_command); +gboolean glade_property_shell_get_use_command (GladePropertyShell *shell); +void glade_property_shell_set_disable_check (GladePropertyShell *shell, + gboolean disable_check); +gboolean glade_property_shell_get_disable_check (GladePropertyShell *shell); + +G_END_DECLS + +#endif /* __GLADE_PROPERTY_SHELL_H__ */ diff --git a/gladeui/glade-property.c b/gladeui/glade-property.c new file mode 100644 index 0000000..746c1a4 --- /dev/null +++ b/gladeui/glade-property.c @@ -0,0 +1,1712 @@ +/* + * Copyright (C) 2001 Ximian, Inc. + * Copyright (C) 2006 The GNOME Foundation. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * Authors: + * Chema Celorio + * Tristan Van Berkom + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +/** + * SECTION:glade-property + * @Title: GladeProperty + * @Short_Description: An interface to properties on the #GladeWidget. + * + * Every object property of every #GladeWidget in every #GladeProject has + * a #GladeProperty to interface with, #GladeProperty provides a means + * to handle properties in the runtime environment. + * + * A #GladeProperty can be seen as an instance of a #GladePropertyDef, + * the #GladePropertyDef describes how a #GladeProperty will function. + */ + +#include +#include /* for atoi and atof */ +#include + +#include + +#include "glade.h" +#include "glade-widget.h" +#include "glade-property.h" +#include "glade-property-def.h" +#include "glade-project.h" +#include "glade-widget-adaptor.h" +#include "glade-debug.h" +#include "glade-app.h" +#include "glade-editor.h" +#include "glade-marshallers.h" + +struct _GladePropertyPrivate { + + GladePropertyDef *def; /* A pointer to the GladeProperty that this + * setting specifies + */ + GladeWidget *widget; /* A pointer to the GladeWidget that this + * GladeProperty is modifying + */ + + GladePropertyState state; /* Current property state, used by editing widgets. + */ + + GValue *value; /* The value of the property + */ + + gchar *insensitive_tooltip; /* Tooltip to display when in insensitive state + * (used to explain why the property is + * insensitive) + */ + + gchar *support_warning; /* Tooltip to display when the property + * has format problems + * (used to explain why the property is + * insensitive) + */ + guint support_disabled : 1; /* Whether this property is disabled due + * to format conflicts + */ + + guint sensitive : 1; /* Whether this property is sensitive (if the + * property is "optional" this takes precedence). + */ + + guint enabled : 1; /* Enabled is a flag that is used for GladeProperties + * that have the optional flag set to let us know + * if this widget has this setting enabled or + * not. (Like default size, it can be specified or + * unspecified). This flag also sets the state + * of the property->input state for the loaded + * widget. + */ + + guint save_always : 1; /* Used to make a special case exception and always + * save this property regardless of what the default + * value is (used for some special cases like properties + * that are assigned initial values in composite widgets + * or derived widget code). + */ + + gint precision; + + /* Used only for translatable strings. */ + guint i18n_translatable : 1; + gchar *i18n_context; + gchar *i18n_comment; + + gint syncing; /* Avoid recursion while synchronizing object with value */ + gint sync_tolerance; + + gchar *bind_source; /* A pointer to the GladeWidget that this + * GladeProperty is bound to + */ + + gchar *bind_property; /* The name of the property from the source + * that is bound to this property + */ + + GBindingFlags bind_flags; /* The flags used in g_object_bind_property() to bind + * this property + */ +}; + +G_DEFINE_TYPE_WITH_PRIVATE (GladeProperty, glade_property, G_TYPE_OBJECT) + +enum +{ + VALUE_CHANGED, + TOOLTIP_CHANGED, + LAST_SIGNAL +}; + +enum +{ + PROP_0, + PROP_CLASS, + PROP_ENABLED, + PROP_SENSITIVE, + PROP_I18N_TRANSLATABLE, + PROP_I18N_CONTEXT, + PROP_I18N_COMMENT, + PROP_STATE, + PROP_PRECISION, + N_PROPERTIES +}; + +static GParamSpec *properties[N_PROPERTIES]; +static guint glade_property_signals[LAST_SIGNAL] = { 0 }; + +/******************************************************************************* + GladeProperty class methods + *******************************************************************************/ +static GladeProperty * +glade_property_dup_impl (GladeProperty *template_prop, GladeWidget *widget) +{ + GladeProperty *property; + + property = g_object_new (GLADE_TYPE_PROPERTY, + "class", template_prop->priv->def, + "i18n-translatable", template_prop->priv->i18n_translatable, + "i18n-context", template_prop->priv->i18n_context, + "i18n-comment", template_prop->priv->i18n_comment, + NULL); + property->priv->widget = widget; + property->priv->value = g_new0 (GValue, 1); + + g_value_init (property->priv->value, template_prop->priv->value->g_type); + + /* Cannot duplicate parentless_widget property */ + if (glade_property_def_parentless_widget (template_prop->priv->def)) + { + if (!G_IS_PARAM_SPEC_OBJECT (glade_property_def_get_pspec (template_prop->priv->def))) + g_warning ("Parentless widget property should be of object type"); + + g_value_set_object (property->priv->value, NULL); + } + else + g_value_copy (template_prop->priv->value, property->priv->value); + + property->priv->enabled = template_prop->priv->enabled; + property->priv->state = template_prop->priv->state; + + glade_property_set_sensitive (property, template_prop->priv->sensitive, + template_prop->priv->insensitive_tooltip); + + return property; +} + +static gboolean +glade_property_equals_value_impl (GladeProperty *property, + const GValue *value) +{ + return !glade_property_def_compare (property->priv->def, property->priv->value, + value); +} + + +static void +glade_property_update_prop_refs (GladeProperty *property, + const GValue *old_value, + const GValue *new_value) +{ + GladeWidget *gold, *gnew; + GObject *old_object, *new_object; + GList *old_list, *new_list, *list, *removed, *added; + + if (GLADE_IS_PARAM_SPEC_OBJECTS (glade_property_def_get_pspec (property->priv->def))) + { + /* Make our own copies incase we're walking an + * unstable list + */ + old_list = g_value_dup_boxed (old_value); + new_list = g_value_dup_boxed (new_value); + + /* Diff up the GList */ + removed = glade_util_removed_from_list (old_list, new_list); + added = glade_util_added_in_list (old_list, new_list); + + /* Adjust the appropriate prop refs */ + for (list = removed; list; list = list->next) + { + old_object = list->data; + gold = glade_widget_get_from_gobject (old_object); + if (gold != NULL) + glade_widget_remove_prop_ref (gold, property); + } + for (list = added; list; list = list->next) + { + new_object = list->data; + gnew = glade_widget_get_from_gobject (new_object); + if (gnew != NULL) + glade_widget_add_prop_ref (gnew, property); + } + + g_list_free (removed); + g_list_free (added); + g_list_free (old_list); + g_list_free (new_list); + } + else + { + if ((old_object = g_value_get_object (old_value)) != NULL) + { + gold = glade_widget_get_from_gobject (old_object); + g_return_if_fail (gold != NULL); + glade_widget_remove_prop_ref (gold, property); + } + + if ((new_object = g_value_get_object (new_value)) != NULL) + { + gnew = glade_widget_get_from_gobject (new_object); + g_return_if_fail (gnew != NULL); + glade_widget_add_prop_ref (gnew, property); + } + } +} + +static gboolean +glade_property_verify (GladeProperty *property, const GValue *value) +{ + gboolean ret = FALSE; + GladeWidget *parent; + + parent = glade_widget_get_parent (property->priv->widget); + + if (glade_property_def_get_is_packing (property->priv->def) && parent) + ret = + glade_widget_adaptor_child_verify_property (glade_widget_get_adaptor (parent), + glade_widget_get_object (parent), + glade_widget_get_object (property->priv->widget), + glade_property_def_id (property->priv->def), + value); + else if (!glade_property_def_get_is_packing (property->priv->def)) + ret = glade_widget_adaptor_verify_property (glade_widget_get_adaptor (property->priv->widget), + glade_widget_get_object (property->priv->widget), + glade_property_def_id (property->priv->def), value); + + return ret; +} + +static void +glade_property_fix_state (GladeProperty *property) +{ + property->priv->state = GLADE_STATE_NORMAL; + + /* Properties are 'changed' state if they are not default, or if + * they are optional and enabled, optional enabled properties + * are saved regardless of default value + */ + if (glade_property_def_optional (property->priv->def)) + { + if (glade_property_get_enabled (property)) + property->priv->state |= GLADE_STATE_CHANGED; + } + else if (property->priv->sensitive && !glade_property_original_default (property)) + property->priv->state |= GLADE_STATE_CHANGED; + + if (property->priv->support_warning) + property->priv->state |= GLADE_STATE_UNSUPPORTED; + + if (property->priv->support_disabled) + property->priv->state |= GLADE_STATE_SUPPORT_DISABLED; + + g_object_notify_by_pspec (G_OBJECT (property), properties[PROP_STATE]); +} + +static void +glade_property_set_precision (GladeProperty *property, gint precision) +{ + property->priv->precision = precision; + + g_object_notify_by_pspec (G_OBJECT (property), properties[PROP_PRECISION]); +} + +static gboolean +glade_property_set_value_impl (GladeProperty *property, const GValue *value) +{ + GladeProject *project = property->priv->widget ? + glade_widget_get_project (property->priv->widget) : NULL; + gboolean changed = FALSE; + GValue old_value = { 0, }; + gboolean warn_before, warn_after; + +#ifdef GLADE_ENABLE_DEBUG + if (glade_get_debug_flags () & GLADE_DEBUG_PROPERTIES) + { + g_print ("PROPERTY: Setting %s property %s on %s ", + glade_property_def_get_is_packing (property->priv->def) ? "packing" : "normal", + glade_property_def_id (property->priv->def), + property->priv->widget ? glade_widget_get_name (property->priv->widget) : "unknown"); + + gchar *str1 = + glade_widget_adaptor_string_from_value (glade_property_def_get_adaptor (property->priv->def), + property->priv->def, property->priv->value); + gchar *str2 = + glade_widget_adaptor_string_from_value (glade_property_def_get_adaptor (property->priv->def), + property->priv->def, value); + g_print ("from %s to %s\n", str1, str2); + g_free (str1); + g_free (str2); + } +#endif /* GLADE_ENABLE_DEBUG */ + + if (!g_value_type_compatible (G_VALUE_TYPE (property->priv->value), G_VALUE_TYPE (value))) + { + g_warning ("Trying to assign an incompatible value to property %s\n", + glade_property_def_id (property->priv->def)); + return FALSE; + } + + /* Check if the backend doesnt give us permission to + * set this value. + */ + if (glade_property_superuser () == FALSE && property->priv->widget && + project && glade_project_is_loading (project) == FALSE && + glade_property_verify (property, value) == FALSE) + { + return FALSE; + } + + /* save "changed" state. + */ + changed = !glade_property_equals_value (property, value); + + /* Add/Remove references from widget ref stacks here + * (before assigning the value) + */ + if (property->priv->widget && changed && + glade_property_def_is_object (property->priv->def)) + glade_property_update_prop_refs (property, property->priv->value, value); + + /* Check pre-changed warning state */ + warn_before = glade_property_warn_usage (property); + + /* Make a copy of the old value */ + g_value_init (&old_value, G_VALUE_TYPE (property->priv->value)); + g_value_copy (property->priv->value, &old_value); + + /* Assign property first so that; if the object need be + * rebuilt, it will reflect the new value + */ + g_value_reset (property->priv->value); + g_value_copy (value, property->priv->value); + + GLADE_PROPERTY_GET_CLASS (property)->sync (property); + + glade_property_fix_state (property); + + if (changed && property->priv->widget) + { + g_signal_emit (G_OBJECT (property), + glade_property_signals[VALUE_CHANGED], + 0, &old_value, property->priv->value); + + glade_project_verify_property (property); + + /* Check post change warning state */ + warn_after = glade_property_warn_usage (property); + + /* Update owning widget's warning state if need be */ + if (property->priv->widget != NULL && warn_before != warn_after) + glade_widget_verify (property->priv->widget); + } + + /* Special case parentless widget properties */ + if (glade_property_def_parentless_widget (property->priv->def)) + { + GladeWidget *gobj; + GObject *obj; + + if ((obj = g_value_get_object (&old_value)) && + (gobj = glade_widget_get_from_gobject (obj))) + glade_widget_show (gobj); + + if ((obj = g_value_get_object (value)) && + (gobj = glade_widget_get_from_gobject (obj))) + glade_widget_hide (gobj); + } + + g_value_unset (&old_value); + return TRUE; +} + +static void +glade_property_get_value_impl (GladeProperty *property, GValue *value) +{ + GParamSpec *pspec; + + pspec = glade_property_def_get_pspec (property->priv->def); + + g_value_init (value, pspec->value_type); + g_value_copy (property->priv->value, value); +} + +static void +glade_property_sync_impl (GladeProperty *property) +{ + GladePropertyPrivate *priv = property->priv; + GladePropertyDef *def = priv->def; + const GValue *value; + const gchar *id; + + /* Heh, here are the many reasons not to + * sync a property ;-) + */ + if (/* the class can be NULL during object, + * construction this is just a temporary state */ + def == NULL || + /* explicit "never sync" flag */ + glade_property_def_get_ignore (def) || + /* recursion guards */ + priv->syncing >= priv->sync_tolerance || + /* No widget owns this property yet */ + priv->widget == NULL) + return; + + id = glade_property_def_id (def); + + /* Only the properties from widget->properties should affect the runtime widget. + * (other properties may be used for convenience in the plugin). + */ + if ((glade_property_def_get_is_packing (def) && + !glade_widget_get_pack_property (priv->widget, id)) + || !glade_widget_get_property (priv->widget, id)) + return; + + priv->syncing++; + + /* optional properties that are disabled get the default runtime value */ + value = (priv->enabled) ? priv->value : glade_property_def_get_default (def); + + /* In the case of construct_only, the widget instance must be rebuilt + * to apply the property + */ + if (glade_property_def_get_construct_only (def) && priv->syncing == 1) + { + /* Virtual properties can be construct only, in which + * case they are allowed to trigger a rebuild, and in + * the process are allowed to get "synced" after the + * instance is rebuilt. + */ + if (glade_property_def_get_virtual (def)) + priv->sync_tolerance++; + + glade_widget_rebuild (priv->widget); + + if (glade_property_def_get_virtual (def)) + priv->sync_tolerance--; + } + else if (glade_property_def_get_is_packing (def)) + glade_widget_child_set_property (glade_widget_get_parent (priv->widget), + priv->widget, id, value); + else + glade_widget_object_set_property (priv->widget, id, value); + + priv->syncing--; +} + +static void +glade_property_load_impl (GladeProperty *property) +{ + GObject *object; + GObjectClass *oclass; + GParamSpec *pspec; + + pspec = glade_property_def_get_pspec (property->priv->def); + + if (property->priv->widget == NULL || + glade_property_def_get_virtual (property->priv->def) || + glade_property_def_get_is_packing (property->priv->def) || + glade_property_def_get_ignore (property->priv->def) || + !(pspec->flags & G_PARAM_READABLE) || G_IS_PARAM_SPEC_OBJECT (pspec)) + return; + + object = glade_widget_get_object (property->priv->widget); + oclass = G_OBJECT_GET_CLASS (object); + + if (g_object_class_find_property (oclass, glade_property_def_id (property->priv->def))) + glade_widget_object_get_property (property->priv->widget, + glade_property_def_id (property->priv->def), + property->priv->value); +} + +/******************************************************************************* + GObjectClass & Object Construction + *******************************************************************************/ +static void +glade_property_set_real_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GladeProperty *property = GLADE_PROPERTY (object); + + switch (prop_id) + { + case PROP_CLASS: + property->priv->def = g_value_get_pointer (value); + break; + case PROP_ENABLED: + glade_property_set_enabled (property, g_value_get_boolean (value)); + break; + case PROP_SENSITIVE: + property->priv->sensitive = g_value_get_boolean (value); + break; + case PROP_I18N_TRANSLATABLE: + glade_property_i18n_set_translatable (property, + g_value_get_boolean (value)); + break; + case PROP_I18N_CONTEXT: + glade_property_i18n_set_context (property, g_value_get_string (value)); + break; + case PROP_I18N_COMMENT: + glade_property_i18n_set_comment (property, g_value_get_string (value)); + break; + case PROP_PRECISION: + glade_property_set_precision (property, g_value_get_int (value)); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_property_get_real_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GladeProperty *property = GLADE_PROPERTY (object); + + switch (prop_id) + { + case PROP_CLASS: + g_value_set_pointer (value, property->priv->def); + break; + case PROP_ENABLED: + g_value_set_boolean (value, glade_property_get_enabled (property)); + break; + case PROP_SENSITIVE: + g_value_set_boolean (value, glade_property_get_sensitive (property)); + break; + case PROP_I18N_TRANSLATABLE: + g_value_set_boolean (value, + glade_property_i18n_get_translatable (property)); + break; + case PROP_I18N_CONTEXT: + g_value_set_string (value, glade_property_i18n_get_context (property)); + break; + case PROP_I18N_COMMENT: + g_value_set_string (value, glade_property_i18n_get_comment (property)); + break; + case PROP_STATE: + g_value_set_int (value, property->priv->state); + break; + case PROP_PRECISION: + g_value_set_int (value, property->priv->precision); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +glade_property_finalize (GObject *object) +{ + GladeProperty *property = GLADE_PROPERTY (object); + + if (property->priv->value) + { + g_value_unset (property->priv->value); + g_free (property->priv->value); + } + if (property->priv->i18n_comment) + g_free (property->priv->i18n_comment); + if (property->priv->i18n_context) + g_free (property->priv->i18n_context); + if (property->priv->support_warning) + g_free (property->priv->support_warning); + if (property->priv->insensitive_tooltip) + g_free (property->priv->insensitive_tooltip); + + G_OBJECT_CLASS (glade_property_parent_class)->finalize (object); +} + +static void +glade_property_init (GladeProperty *property) +{ + property->priv = glade_property_get_instance_private (property); + + property->priv->precision = 2; + property->priv->enabled = TRUE; + property->priv->sensitive = TRUE; + property->priv->i18n_translatable = TRUE; + property->priv->i18n_comment = NULL; + property->priv->sync_tolerance = 1; +} + +static void +glade_property_class_init (GladePropertyClass * prop_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (prop_class); + + /* GObjectClass */ + object_class->set_property = glade_property_set_real_property; + object_class->get_property = glade_property_get_real_property; + object_class->finalize = glade_property_finalize; + + /* Class methods */ + prop_class->dup = glade_property_dup_impl; + prop_class->equals_value = glade_property_equals_value_impl; + prop_class->set_value = glade_property_set_value_impl; + prop_class->get_value = glade_property_get_value_impl; + prop_class->sync = glade_property_sync_impl; + prop_class->load = glade_property_load_impl; + prop_class->value_changed = NULL; + prop_class->tooltip_changed = NULL; + + /* Properties */ + properties[PROP_CLASS] = + g_param_spec_pointer ("class", + _("Class"), + _("The GladePropertyDef for this property"), + G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE); + + properties[PROP_ENABLED] = + g_param_spec_boolean ("enabled", + _("Enabled"), + _("If the property is optional, this is its enabled state"), + TRUE, G_PARAM_READWRITE); + + properties[PROP_SENSITIVE] = + g_param_spec_boolean ("sensitive", + _("Sensitive"), + _("This gives backends control to set property sensitivity"), + TRUE, G_PARAM_READWRITE); + + properties[PROP_I18N_CONTEXT] = + g_param_spec_string ("i18n-context", + _("Context"), + _("Context for translation"), + NULL, + G_PARAM_READWRITE); + + properties[PROP_I18N_COMMENT] = + g_param_spec_string ("i18n-comment", + _("Comment"), + _("Comment for translators"), + NULL, + G_PARAM_READWRITE); + + properties[PROP_I18N_TRANSLATABLE] = + g_param_spec_boolean ("i18n-translatable", + _("Translatable"), + _("Whether this property is translatable"), + TRUE, + G_PARAM_READWRITE); + + properties[PROP_STATE] = + g_param_spec_int ("state", + _("Visual State"), + _("Priority information for the property editor to act on"), + GLADE_STATE_NORMAL, + G_MAXINT, + GLADE_STATE_NORMAL, + G_PARAM_READABLE); + + properties[PROP_PRECISION] = + g_param_spec_int ("precision", + _("Precision"), + _("Where applicable, precision to use on editors"), + 0, + G_MAXINT, + 2, + G_PARAM_READWRITE); + + /* Install all properties */ + g_object_class_install_properties (object_class, N_PROPERTIES, properties); + + /* Signal */ + glade_property_signals[VALUE_CHANGED] = + g_signal_new ("value-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladePropertyClass, + value_changed), + NULL, NULL, + _glade_marshal_VOID__POINTER_POINTER, + G_TYPE_NONE, 2, G_TYPE_POINTER, G_TYPE_POINTER); + + glade_property_signals[TOOLTIP_CHANGED] = + g_signal_new ("tooltip-changed", + G_TYPE_FROM_CLASS (object_class), + G_SIGNAL_RUN_LAST, + G_STRUCT_OFFSET (GladePropertyClass, + tooltip_changed), + NULL, NULL, + _glade_marshal_VOID__STRING_STRING_STRING, + G_TYPE_NONE, 3, G_TYPE_STRING, G_TYPE_STRING, + G_TYPE_STRING); +} + +/******************************************************************************* + API + *******************************************************************************/ +/** + * glade_property_new: + * @def: A #GladePropertyDef defining this property + * @widget: The #GladeWidget this property is created for + * @value: The initial #GValue of the property or %NULL + * (the #GladeProperty will assume ownership of @value) + * + * Creates a #GladeProperty of type @klass for @widget with @value; if + * @value is %NULL, then the introspected default value for that property + * will be used. + * + * Returns: The newly created #GladeProperty + */ +GladeProperty * +glade_property_new (GladePropertyDef *def, + GladeWidget *widget, + GValue *value) +{ + GladeProperty *property; + + g_return_val_if_fail (GLADE_IS_PROPERTY_DEF (def), NULL); + + property = (GladeProperty *) g_object_new (GLADE_TYPE_PROPERTY, NULL); + property->priv->def = def; + property->priv->widget = widget; + property->priv->value = value; + + if (glade_property_def_optional (def)) + property->priv->enabled = glade_property_def_optional_default (def); + + if (property->priv->value == NULL) + { + const GValue *orig_def = + glade_property_def_get_original_default (def); + + property->priv->value = g_new0 (GValue, 1); + g_value_init (property->priv->value, orig_def->g_type); + g_value_copy (orig_def, property->priv->value); + } + + return property; +} + +/** + * glade_property_dup: + * @template_prop: A #GladeProperty + * @widget: A #GladeWidget + * + * Returns: (transfer full): A newly duplicated property based on the new widget + */ +GladeProperty * +glade_property_dup (GladeProperty *template_prop, GladeWidget *widget) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY (template_prop), NULL); + return GLADE_PROPERTY_GET_CLASS (template_prop)->dup (template_prop, widget); +} + +static void +glade_property_reset_common (GladeProperty *property, gboolean original) +{ + const GValue *value; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + + if (original) + value = glade_property_def_get_original_default (property->priv->def); + else + value = glade_property_def_get_default (property->priv->def); + + GLADE_PROPERTY_GET_CLASS (property)->set_value (property, value); +} + +/** + * glade_property_reset: + * @property: A #GladeProperty + * + * Resets this property to its default value + */ +void +glade_property_reset (GladeProperty *property) +{ + glade_property_reset_common (property, FALSE); +} + +/** + * glade_property_original_reset: + * @property: A #GladeProperty + * + * Resets this property to its original default value + */ +void +glade_property_original_reset (GladeProperty *property) +{ + glade_property_reset_common (property, TRUE); +} + +static gboolean +glade_property_default_common (GladeProperty *property, gboolean orig) +{ + const GValue *value; + + g_return_val_if_fail (GLADE_IS_PROPERTY (property), FALSE); + + if (orig) + value = glade_property_def_get_original_default (property->priv->def); + else + value = glade_property_def_get_default (property->priv->def); + + return GLADE_PROPERTY_GET_CLASS (property)->equals_value (property, value); +} + +/** + * glade_property_default: + * @property: A #GladeProperty + * + * Returns: Whether this property is at its default value + */ +gboolean +glade_property_default (GladeProperty *property) +{ + return glade_property_default_common (property, FALSE); +} + +/** + * glade_property_original_default: + * @property: A #GladeProperty + * + * Returns: Whether this property is at its original default value + */ +gboolean +glade_property_original_default (GladeProperty *property) +{ + return glade_property_default_common (property, TRUE); +} + +/** + * glade_property_equals_value: + * @property: a #GladeProperty + * @value: a #GValue + * + * Returns: Whether this property is equal to the value provided + */ +gboolean +glade_property_equals_value (GladeProperty *property, const GValue *value) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY (property), FALSE); + return GLADE_PROPERTY_GET_CLASS (property)->equals_value (property, value); +} + +/** + * glade_property_equals_va_list: + * @property: a #GladeProperty + * @vl: a va_list + * + * Returns: Whether this property is equal to the value provided + */ +static gboolean +glade_property_equals_va_list (GladeProperty *property, va_list vl) +{ + GValue *value; + gboolean ret; + + g_return_val_if_fail (GLADE_IS_PROPERTY (property), FALSE); + + value = glade_property_def_make_gvalue_from_vl (property->priv->def, vl); + + ret = GLADE_PROPERTY_GET_CLASS (property)->equals_value (property, value); + + g_value_unset (value); + g_free (value); + return ret; +} + +/** + * glade_property_equals: + * @property: a #GladeProperty + * @...: a provided property value + * + * Returns: Whether this property is equal to the value provided + */ +gboolean +glade_property_equals (GladeProperty *property, ...) +{ + va_list vl; + gboolean ret; + + g_return_val_if_fail (GLADE_IS_PROPERTY (property), FALSE); + + va_start (vl, property); + ret = glade_property_equals_va_list (property, vl); + va_end (vl); + + return ret; +} + +/** + * glade_property_set_value: + * @property: a #GladeProperty + * @value: a #GValue + * + * Sets the property's value + * + * Returns: Whether the property was successfully set. + */ +gboolean +glade_property_set_value (GladeProperty *property, const GValue *value) +{ + g_return_val_if_fail (GLADE_IS_PROPERTY (property), FALSE); + g_return_val_if_fail (value != NULL, FALSE); + return GLADE_PROPERTY_GET_CLASS (property)->set_value (property, value); +} + +/** + * glade_property_set_va_list: + * @property: a #GladeProperty + * @vl: a va_list with value to set + * + * Sets the property's value + */ +gboolean +glade_property_set_va_list (GladeProperty *property, va_list vl) +{ + GValue *value; + gboolean success; + + g_return_val_if_fail (GLADE_IS_PROPERTY (property), FALSE); + + value = glade_property_def_make_gvalue_from_vl (property->priv->def, vl); + + success = GLADE_PROPERTY_GET_CLASS (property)->set_value (property, value); + + g_value_unset (value); + g_free (value); + + return success; +} + +/** + * glade_property_set: + * @property: a #GladeProperty + * @...: the value to set + * + * Sets the property's value (in a convenient way) + */ +gboolean +glade_property_set (GladeProperty *property, ...) +{ + va_list vl; + gboolean success; + + g_return_val_if_fail (GLADE_IS_PROPERTY (property), FALSE); + + va_start (vl, property); + success = glade_property_set_va_list (property, vl); + va_end (vl); + + return success; +} + +/** + * glade_property_get_value: + * @property: a #GladeProperty + * @value: a #GValue + * + * Retrieve the property value + */ +void +glade_property_get_value (GladeProperty *property, GValue *value) +{ + g_return_if_fail (GLADE_IS_PROPERTY (property)); + g_return_if_fail (value != NULL); + GLADE_PROPERTY_GET_CLASS (property)->get_value (property, value); +} + +/** + * glade_property_get_default: + * @property: a #GladeProperty + * @value: a #GValue + * + * Retrieve the default property value + */ +void +glade_property_get_default (GladeProperty *property, GValue *value) +{ + GParamSpec *pspec; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + g_return_if_fail (value != NULL); + + pspec = glade_property_def_get_pspec (property->priv->def); + g_value_init (value, pspec->value_type); + g_value_copy (glade_property_def_get_default (property->priv->def), value); +} + +/** + * glade_property_get_va_list: + * @property: a #GladeProperty + * @vl: a va_list + * + * Retrieve the property value + */ +void +glade_property_get_va_list (GladeProperty *property, va_list vl) +{ + g_return_if_fail (GLADE_IS_PROPERTY (property)); + glade_property_def_set_vl_from_gvalue (property->priv->def, property->priv->value, + vl); +} + +/** + * glade_property_get: + * @property: a #GladeProperty + * @...: An address to store the value + * + * Retrieve the property value + */ +void +glade_property_get (GladeProperty *property, ...) +{ + va_list vl; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + + va_start (vl, property); + glade_property_get_va_list (property, vl); + va_end (vl); +} + +/** + * glade_property_sync: + * @property: a #GladeProperty + * + * Synchronize the object with this property + */ +void +glade_property_sync (GladeProperty *property) +{ + g_return_if_fail (GLADE_IS_PROPERTY (property)); + GLADE_PROPERTY_GET_CLASS (property)->sync (property); +} + +/** + * glade_property_load: + * @property: a #GladeProperty + * + * Loads the value of @property from the corresponding object instance + */ +void +glade_property_load (GladeProperty *property) +{ + g_return_if_fail (GLADE_IS_PROPERTY (property)); + GLADE_PROPERTY_GET_CLASS (property)->load (property); +} + +/** + * glade_property_read: + * @property: a #GladeProperty or #NULL + * @project: the #GladeProject + * @node: the #GladeXmlNode to read, will either be a 'widget' + * node or a 'child' node for packing properties. + * + * Read the value and any attributes for @property from @node, assumes + * @property is being loaded for @project + * + * Note that object values will only be resolved after the project is + * completely loaded + */ +void +glade_property_read (GladeProperty *property, + GladeProject *project, + GladeXmlNode *prop) +{ + GValue *gvalue = NULL; + gchar /* *id, *name, */ * value; + gint translatable = FALSE; + gchar *comment = NULL, *context = NULL; + gchar *bind_flags = NULL; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + g_return_if_fail (GLADE_IS_PROJECT (project)); + g_return_if_fail (prop != NULL); + + if (!glade_xml_node_verify (prop, GLADE_XML_TAG_PROPERTY)) + return; + + if (!(value = glade_xml_get_content (prop))) + return; + + /* If an optional property is specified in the + * glade file, its enabled + */ + property->priv->enabled = TRUE; + + if (glade_property_def_is_object (property->priv->def)) + { + /* we must synchronize this directly after loading this project + * (i.e. lookup the actual objects after they've been parsed and + * are present). + */ + g_object_set_data_full (G_OBJECT (property), + "glade-loaded-object", g_strdup (value), g_free); + } + else + { + gvalue = + glade_property_def_make_gvalue_from_string (property->priv->def, value, project); + + GLADE_PROPERTY_GET_CLASS (property)->set_value (property, gvalue); + + g_value_unset (gvalue); + g_free (gvalue); + } + + translatable = + glade_xml_get_property_boolean (prop, GLADE_TAG_TRANSLATABLE, FALSE); + comment = glade_xml_get_property_string (prop, GLADE_TAG_COMMENT); + context = glade_xml_get_property_string (prop, GLADE_TAG_CONTEXT); + + property->priv->bind_source = glade_xml_get_property_string (prop, GLADE_TAG_BIND_SOURCE); + property->priv->bind_property = glade_xml_get_property_string (prop, GLADE_TAG_BIND_PROPERTY); + bind_flags = glade_xml_get_property_string (prop, GLADE_TAG_BIND_FLAGS); + if (bind_flags) + property->priv->bind_flags = glade_property_def_make_flags_from_string (G_TYPE_BINDING_FLAGS, bind_flags); + + glade_property_i18n_set_translatable (property, translatable); + glade_property_i18n_set_comment (property, comment); + glade_property_i18n_set_context (property, context); + + g_free (comment); + g_free (context); + g_free (value); + g_free (bind_flags); +} + + +/** + * glade_property_write: + * @property: a #GladeProperty + * @context: A #GladeXmlContext + * @node: A #GladeXmlNode + * + * Write @property to @node + */ +void +glade_property_write (GladeProperty *property, + GladeXmlContext *context, + GladeXmlNode *node) +{ + GladeXmlNode *prop_node; + gchar *value; + gboolean save_always; + gchar *binding_flags = NULL; + GFlagsClass *flags_class; + GFlagsValue flags_value; + guint i; + + g_return_if_fail (GLADE_IS_PROPERTY (property)); + g_return_if_fail (node != NULL); + + /* This code should work the same for , and